Compare commits

..
29 Commits
Author SHA1 Message Date
Alexandru Macocian b27998d7f2 Basic support for texmod/umod. 2021-04-15 11:15:36 +02:00
amacocianandGitHub 98d3e21db9 Update README.md 2021-04-15 10:17:24 +02:00
Alexandru Macocian 0683504330 Minor gui fixes.
Set default webpages in application configuration.
Option to auto-launch gwtoolbox.
2021-04-15 09:57:19 +02:00
Alexandru Macocian 98bd1b2de6 Periodically check for updates.
Reorder settings in the settings category view.
2021-04-14 15:15:56 +02:00
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
Alexandru Macocian 2c35333cac Handle missing dependency on webview2 browser. 2021-04-10 11:18:46 +02:00
Alexandru Macocian fe5e38b216 Change update process to modify and restore execution policy before and after update.
Change update process to wait for the client to close instead of a static wait.
2021-04-10 10:31:36 +02:00
Alexandru Macocian 9fd7d8b62a Ask for execution policy change before running script. 2021-04-09 17:19:34 +02:00
Alexandru Macocian a9aa73033f Update to 0.2 2021-04-09 15:56:11 +02:00
Alexandru Macocian 73dfca0bc0 Include updater in main application. 2021-04-09 15:52:33 +02:00
Alexandru Macocian cdc9b6b011 Overwrite files during extraction 2021-04-09 13:36:29 +02:00
Alexandru Macocian bbdd365a28 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-09 13:33:36 +02:00
Alexandru Macocian ec19ce6561 Source code for updater utility 2021-04-09 13:33:31 +02:00
amacocianandGitHub 9e62860d8d Update README.md 2021-04-09 12:31:27 +02:00
Alexandru Macocian 4b6d6cf651 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-09 12:20:05 +02:00
Alexandru Macocian 4af457710f Implement GWToolbox launcher. 2021-04-09 12:19:58 +02:00
amacocianandGitHub f4dc05d7d0 Update README.md 2021-04-08 16:08:44 +02:00
68 changed files with 3089 additions and 232 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,22 +1,28 @@
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("CharacterName")]
public string CharacterName { get; set; }
[JsonProperty("ToolboxPath")]
public string ToolboxPath { get; set; }
[JsonProperty("TexmodPath")]
public string TexmodPath { get; set; }
[JsonProperty("ToolboxAutoLaunch")]
public bool ToolboxAutoLaunch { get; set; }
[JsonProperty("LeftBrowserDefault")]
public string LeftBrowserDefault { get; set; }
public string LeftBrowserDefault { get; set; } = "https://gwpvx.fandom.com/wiki/PvX_wiki";
[JsonProperty("RightBrowserDefault")]
public string RightBrowserDefault { get; set; }
[JsonProperty("ProtectedUsername")]
public string ProtectedUsername { get; set; }
[JsonProperty("ProtectedPassword")]
public string ProtectedPassword { get; set; }
public string RightBrowserDefault { get; set; } = "https://wiki.guildwars.com/wiki/Quick_access_links";
[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,12 @@
using Newtonsoft.Json;
namespace Daybreak.Configuration
{
public sealed class ExperimentalFeatures
{
[JsonProperty("MultiLaunchSupport")]
public bool MultiLaunchSupport { get; set; }
[JsonProperty("ToolboxAutoLaunchDelay")]
public int ToolboxAutoLaunchDelay { get; set; } = 5000;
}
}
+14 -5
View File
@@ -1,13 +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;
@@ -25,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<CoreWebView2Environment, CoreWebView2Environment>((sp) => TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null)));
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -37,13 +39,20 @@ namespace Daybreak.Configuration
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
viewProducer.ThrowIfNull(nameof(viewProducer));
viewProducer.RegisterView<StartupView>();
viewProducer.RegisterView<MainView>();
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, UpdateSourceTrigger=PropertyChanged}" 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, UpdateSourceTrigger=PropertyChanged}" 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>
+88
View File
@@ -0,0 +1,88 @@
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.DataContext.As<LoginCredentials>().Username = this.Username;
}
private void CharacterNameTextbox_TextChanged(object sender, EventArgs e)
{
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;
}
}
}
+21 -3
View File
@@ -19,8 +19,9 @@
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"></wv2:WebView2>
<Grid Grid.Row="1" Background="#80808080">
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></wv2:WebView2>
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
@@ -51,15 +52,32 @@
Grid.Column="1" IsReadOnly="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=OneWay}" Background="Transparent"
BorderThickness="1" VerticalAlignment="Center" VerticalContentAlignment="Center"
BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
PreviewKeyDown="TextBox_PreviewKeyDown"></TextBox>
PreviewKeyDown="TextBox_PreviewKeyDown"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></TextBox>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<local:StarGlyph Height="30" Width="30" Margin="5"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
Clicked="StarGlyph_Clicked" x:Name="FavoriteButton"></local:StarGlyph>
<local:MaximizeButton Height="30" Width="30" Margin="5"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
Clicked="MaximizeButton_Clicked"></local:MaximizeButton>
</StackPanel>
</Grid>
<Grid Grid.RowSpan="2" Background="Gray"
Visibility="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay,Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Foreground="White" Text="Browser not supported." FontSize="26" TextWrapping="Wrap"></TextBlock>
<TextBlock Foreground="White" Text="Download the Evergreen Bootstrapper from here:" FontSize="26" TextWrapping="Wrap" />
<TextBox Background="Transparent" BorderBrush="Transparent" BorderThickness="0"
Text="https://go.microsoft.com/fwlink/p/?LinkId=2124703"
IsReadOnly="True" Margin="0, 0, 0, 30" FontSize="22" Foreground="Blue"
PreviewMouseLeftButtonDown="Hyperlink_PreviewMouseLeftButtonDown" Cursor="Hand"
TextWrapping="Wrap"></TextBox>
<TextBlock Foreground="White" Text="Restart after the installation." FontSize="26" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Grid>
</UserControl>
@@ -1,8 +1,12 @@
using Daybreak.Launch;
using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Web.WebView2.Core;
using System;
using System.Diagnostics;
using System.Extensions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -14,16 +18,20 @@ namespace Daybreak.Controls
/// </summary>
public partial class ChromiumBrowserWrapper : UserControl
{
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
public readonly static DependencyProperty AddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
public readonly static DependencyProperty FavoriteAddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(FavoriteAddress));
public readonly static DependencyProperty NavigatingProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(Navigating));
public readonly static DependencyProperty AddressBarReadonlyProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(AddressBarReadonly));
public readonly static DependencyProperty BrowserSupportedProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserSupported), new PropertyMetadata(true));
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
private readonly CoreWebView2Environment coreWebView2Environment;
private readonly IConfigurationManager configurationManager;
private readonly ILogger logger;
private CoreWebView2Environment coreWebView2Environment;
public string Address
{
@@ -45,12 +53,18 @@ namespace Daybreak.Controls
get => this.GetTypedValue<bool>(AddressBarReadonlyProperty);
private set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
}
public bool BrowserSupported
{
get => this.GetTypedValue<bool>(BrowserSupportedProperty);
private set => this.SetTypedValue<bool>(BrowserSupportedProperty, value);
}
public ChromiumBrowserWrapper()
{
this.coreWebView2Environment = Launcher.ApplicationServiceManager.GetService<CoreWebView2Environment>();
this.configurationManager = Launcher.ApplicationServiceManager.GetService<IConfigurationManager>();
this.logger = Launcher.ApplicationServiceManager.GetService<ILogger>();
this.InitializeComponent();
this.InitializeEnvironment();
this.InitializeBrowser();
}
@@ -68,13 +82,51 @@ namespace Daybreak.Controls
this.InitializeBrowser();
}
private async void InitializeBrowser()
private void InitializeEnvironment()
{
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
try
{
this.coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
this.BrowserSupported = true;
}
catch(Exception e)
{
this.logger.LogWarning($"Browser initialization failed. Details: {e}");
this.BrowserSupported = false;
}
}
private async Task InitializeBrowser()
{
if (this.BrowserSupported is true)
{
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
}
}
private async void RetryInitializeButton_Clicked(object sender, EventArgs e)
{
this.InitializeEnvironment();
try
{
await this.InitializeBrowser();
}
catch
{
this.BrowserSupported = false;
}
}
private void Hyperlink_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (Uri.TryCreate(BrowserDownloadLink, UriKind.Absolute, out var uri))
{
Process.Start("explorer.exe", uri.ToString());
}
}
private void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
+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, UpdateSourceTrigger=PropertyChanged}"
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,79 @@
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.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.1.2</Version>
<Version>0.6.3</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)
{
}
}
}
+32 -3
View File
@@ -1,5 +1,6 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Pepa.Wpf.Utilities;
@@ -26,6 +27,7 @@ namespace Daybreak.Launch
private readonly IViewManager viewManager;
private readonly IScreenshotProvider screenshotProvider;
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly CancellationTokenSource cancellationToken = new();
public string CreditText
@@ -37,18 +39,20 @@ namespace Daybreak.Launch
public MainWindow(
IViewManager viewManager,
IScreenshotProvider screenshotProvider,
IBloogumClient bloogumClient)
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.viewManager.ShowView<StartupView>();
this.SetupImageCycle();
this.CheckForUpdates();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -116,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)
@@ -145,6 +149,31 @@ namespace Daybreak.Launch
}
}
private async void CheckForUpdates()
{
var updateAvailable = await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
if (updateAvailable)
{
this.viewManager.ShowView<AskUpdateView>();
}
else
{
this.viewManager.ShowView<MainView>();
this.PeriodicallyCheckForUpdates();
}
}
private void PeriodicallyCheckForUpdates()
{
TaskExtensions.RunPeriodicAsync(async () =>
{
if (await this.applicationUpdater.UpdateAvailable())
{
this.Dispatcher.Invoke(() => this.viewManager.ShowView<AskUpdateView>());
}
}, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15), CancellationToken.None);
}
private static Color GetAverageColor(BitmapSource bitmap)
{
var format = bitmap.Format;
+13
View File
@@ -0,0 +1,13 @@
namespace Daybreak.Models
{
public enum ExecutionPolicies
{
AllSigned,
Bypass,
Default,
RemoteSigned,
Restricted,
Undefined,
Unrestricted
}
}
+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; }
}
}
+44
View File
@@ -0,0 +1,44 @@
using System.ComponentModel;
namespace Daybreak.Models
{
public sealed class UpdateStatus : INotifyPropertyChanged
{
public static readonly UpdateStep StartingStep = new("Starting");
public static readonly UpdateStep CheckingLatestVersion = new("Checking latest version");
public static UpdateStep Downloading(double progress) => new DownloadUpdateStep("Downloading", progress);
public static readonly UpdateStep DownloadFinished = new("Download finished. Application will restart in order to apply the update.");
private UpdateStep currentStep = StartingStep;
public event PropertyChangedEventHandler PropertyChanged;
public UpdateStep CurrentStep
{
get => this.currentStep;
set
{
this.currentStep = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public class UpdateStep
{
public string Name { get; }
internal UpdateStep(string name)
{
this.Name = name;
}
}
public class DownloadUpdateStep : UpdateStep
{
internal DownloadUpdateStep(string name, double progress) : base(name)
{
this.Progress = progress;
}
public double Progress { get; }
}
}
}
@@ -1,64 +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 ProcessName = "gw";
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
public bool IsGuildwarsRunning => GuildwarsProcessDetected();
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");
});
}
private static bool GuildwarsProcessDetected()
{
var process = Process.GetProcessesByName(ProcessName).FirstOrDefault();
return process is not null;
}
}
}
@@ -1,8 +0,0 @@
namespace Daybreak.Services.ApplicationDetection
{
public interface IApplicationDetector
{
bool IsGuildwarsRunning { get; }
void LaunchGuildwars();
}
}
@@ -0,0 +1,233 @@
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 Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Daybreak.Services.ApplicationLauncher
{
public class ApplicationLauncher : IApplicationLauncher
{
private const string TexModProcessName = "TexMod";
private const string UModProcessName = "uMod";
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 IsTexmodRunning => TexModProcessDetected();
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}");
}
});
}
public Task LaunchTexmod()
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var executable = configuration.TexmodPath;
if (File.Exists(executable) is false)
{
throw new ExecutableNotFoundException($"Texmod 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;
}
return Process.GetProcessesByName(ProcessName).Where(process => string.Equals(path.Path, process.MainModule.FileName, StringComparison.Ordinal)).Any();
}
catch
{
return true;
}
}
return Process.GetProcessesByName(ProcessName).Any();
}
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).Any();
}
private static bool TexModProcessDetected()
{
return Process.GetProcesses()
.Where(process => string.Equals(process.ProcessName, UModProcessName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(process.ProcessName, TexModProcessName, StringComparison.OrdinalIgnoreCase)).Any();
}
}
}
@@ -0,0 +1,14 @@
using System.Threading.Tasks;
namespace Daybreak.Services.ApplicationLauncher
{
public interface IApplicationLauncher
{
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
bool IsTexmodRunning { get; }
Task LaunchGuildwars();
Task LaunchGuildwarsToolbox();
Task LaunchTexmod();
}
}
@@ -1,7 +1,9 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
using System.Extensions;
using System.IO;
namespace Daybreak.Services.Configuration
@@ -11,9 +13,11 @@ namespace Daybreak.Services.Configuration
private const string ConfigName = "Daybreak.config.json";
private ApplicationConfiguration applicationConfiguration;
private readonly ILogger logger;
public ConfigurationManager()
public ConfigurationManager(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
try
{
var serializedConfig = File.ReadAllText(ConfigName);
@@ -21,7 +25,8 @@ namespace Daybreak.Services.Configuration
}
catch(Exception e)
{
throw new FatalException("Failed to load application configuration. See inner exception for details", e);
this.logger.LogWarning($"No configuration detected. Loading default configuration. Details: {e}");
this.applicationConfiguration = new ApplicationConfiguration();
}
}
@@ -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;
}
}
}
@@ -0,0 +1,326 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
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.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string ExecutionPolicyKey = "ExecutionPolicy";
private const string UpdatedKey = "Updating";
private const string RegistryKey = "Daybreak";
private const string ExtractAndRunPs1 = "ExtractAndRun.ps1";
private const string TempFile = "tempfile.zip";
private const string VersionTag = "{VERSION}";
private const string InputFileTag = "{INPUTFILE}";
private const string OutputPathTag = "{OUTPUTPATH}";
private const string ExecutionPolicyTag = "{EXECUTIONPOLICY}";
private const string ProcessIdTag = "{PROCESSID}";
private const string Url = "https://github.com/AlexMacocian/Daybreak/releases/latest";
private const string DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/v{VersionTag}/Daybreakv{VersionTag}.zip";
private const string GetExecutionPolicyCommand = "Get-ExecutionPolicy -Scope CurrentUser";
private const string SetExecutionPolicyCommand = $"Set-ExecutionPolicy {ExecutionPolicyTag} -Scope CurrentUser";
private const string WaitCommand = $"Wait-Process -Id {ProcessIdTag}";
private const string ExtractCommandTemplate = $"Expand-Archive -Path '{InputFileTag}' -DestinationPath '{OutputPathTag}' -Force";
private const string RunClientCommand = @".\Daybreak.exe";
private const string RemoveTempFile = $"Remove-item {TempFile}";
private const string RemovePs1 = $"Remove-item {ExtractAndRunPs1}";
private readonly ILogger logger;
private readonly HttpClient httpClient = new();
public string CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
public ApplicationUpdater(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
public async Task<bool> DownloadUpdate(UpdateStatus updateStatus)
{
updateStatus.CurrentStep = UpdateStatus.CheckingLatestVersion;
var latestVersion = (await this.GetLatestVersion()).ExtractValue();
if (latestVersion is null)
{
this.logger.LogWarning("Failed to retrieve latest version. Aborting update");
return false;
}
using var downloadLatestResponse = await this.httpClient.GetAsync(
DownloadUrl.Replace(VersionTag, latestVersion));
if (downloadLatestResponse.IsSuccessStatusCode is false)
{
this.logger.LogWarning("Failed to download latest version. Aborting udpate");
return false;
}
this.logger.LogInformation("Beginning update download");
var downloadStream = await downloadLatestResponse.Content.ReadAsStreamAsync();
var fileStream = File.OpenWrite(TempFile);
var downloadSize = (double)downloadStream.Length;
var buffer = new byte[1024];
var length = 0;
double downloaded = 0;
var tickTime = DateTime.Now;
while (downloadStream.CanRead && (length = await downloadStream.ReadAsync(buffer)) > 0)
{
downloaded += length;
await fileStream.WriteAsync(buffer, 0, length);
if ((DateTime.Now - tickTime).TotalMilliseconds > 50)
{
tickTime = DateTime.Now;
updateStatus.CurrentStep = UpdateStatus.Downloading(downloaded / downloadSize);
}
}
updateStatus.CurrentStep = UpdateStatus.Downloading(1);
updateStatus.CurrentStep = UpdateStatus.DownloadFinished;
fileStream.Close();
this.logger.LogInformation("Downloaded update");
return true;
}
public async Task<bool> UpdateAvailable()
{
var version = string.Join('.', this.CurrentVersion.Split('.'));
var maybeLatestVersion = await this.GetLatestVersion();
return maybeLatestVersion.Switch(
onSome: latestVersion => string.Compare(version, latestVersion, true) < 0,
onNone: () =>
{
this.logger.LogWarning("Failed to retrieve latest version");
return false;
}).ExtractValue();
}
public void FinalizeUpdate()
{
var maybeExecutionPolicy = this.RetrieveExecutionPolicy();
maybeExecutionPolicy.DoAny(
onNone: () =>
{
throw new InvalidOperationException("Failed to retrieve execution policy");
});
var executionPolicy = maybeExecutionPolicy.ExtractValue();
if (executionPolicy is not ExecutionPolicies.Bypass ||
executionPolicy is not ExecutionPolicies.Unrestricted)
{
this.logger.LogInformation($"Execution policy is set to {executionPolicy}. Setting to {ExecutionPolicies.Bypass}");
}
SaveExecutionPolicyValueToRegistry(executionPolicy);
MarkUpdateInRegistry();
this.SetExecutionPolicy(ExecutionPolicies.Bypass);
this.LaunchExtractor();
}
public void OnStartup()
{
if (UpdateMarkedInRegistry())
{
UnmarkUpdateInRegistry();
var maybeExecutionPolicy = LoadExecutionPolicyValueFromRegistry();
maybeExecutionPolicy.Do(
onSome: policy =>
{
SetExecutionPolicy(policy);
},
onNone: () =>
{
throw new InvalidOperationException("Found update marked in registry but no execution policy");
});
}
}
public void OnClosing()
{
}
private async Task<Optional<string>> GetLatestVersion()
{
using var response = await this.httpClient.GetAsync(Url);
if (response.IsSuccessStatusCode)
{
var versionTag = response.RequestMessage.RequestUri.ToString().Split('/').Last().TrimStart('v');
return versionTag;
}
return Optional.None<string>();
}
private Optional<ExecutionPolicies> RetrieveExecutionPolicy()
{
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = GetExecutionPolicyCommand,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
process.Start();
this.logger.LogInformation("Checking current execution policy");
var output = process.StandardOutput.ReadToEnd();
if (!Enum.TryParse(typeof(ExecutionPolicies), output, out var executionPolicy))
{
var error = process.StandardError.ReadToEnd();
this.logger.LogError($"Failed to retrieve current user execution policy. Stdout: {output}. Stderr: {error}");
return Optional.None<ExecutionPolicies>();
}
return executionPolicy.Cast<ExecutionPolicies>();
}
private void SetExecutionPolicy(ExecutionPolicies executionPolicy)
{
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = SetExecutionPolicyCommand.Replace(ExecutionPolicyTag, executionPolicy.ToString()),
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
process.Start();
this.logger.LogInformation($"Setting execution policy to {executionPolicy}");
var output = process.StandardOutput.ReadToEnd();
if (!string.IsNullOrWhiteSpace(output))
{
var error = process.StandardError.ReadToEnd();
throw new InvalidOperationException($"Failed to set execution policy to {executionPolicy}. Stdout: {output}. Stderr: {error}");
}
}
private void LaunchExtractor()
{
File.WriteAllLines(ExtractAndRunPs1, new List<string>()
{
WaitCommand.Replace(ProcessIdTag, Environment.ProcessId.ToString()),
ExtractCommandTemplate
.Replace(InputFileTag, Path.GetFullPath(TempFile))
.Replace(OutputPathTag, Directory.GetCurrentDirectory()),
RemoveTempFile,
RemovePs1,
RunClientCommand
});
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $@"{Directory.GetCurrentDirectory()}\{ExtractAndRunPs1}",
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Maximized,
WorkingDirectory = Directory.GetCurrentDirectory()
},
};
this.logger.LogInformation("Created extractor script. Attempting to launch powershell");
if (process.Start() is false)
{
throw new InvalidOperationException("Failed to create and start powershell script");
}
}
private static void MarkUpdateInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(UpdatedKey, true);
homeRegistryKey.Close();
}
private static void UnmarkUpdateInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(UpdatedKey, false);
homeRegistryKey.Close();
}
private static bool UpdateMarkedInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
var update = homeRegistryKey.GetValue(UpdatedKey);
homeRegistryKey.Close();
if (update is string updateString)
{
if (bool.TryParse(updateString, out var updateValue))
{
return updateValue;
}
else
{
throw new InvalidOperationException($"Found update value {updateString} in registry");
}
}
return false;
}
private static void SaveExecutionPolicyValueToRegistry(ExecutionPolicies executionPolicy)
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(ExecutionPolicyKey, executionPolicy.ToString());
homeRegistryKey.Close();
}
private static Optional<ExecutionPolicies> LoadExecutionPolicyValueFromRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
var executionPolicy = homeRegistryKey.GetValue(ExecutionPolicyKey);
homeRegistryKey.Close();
if (executionPolicy is null)
{
return Optional.None<ExecutionPolicies>();
}
else if (executionPolicy is string executionPolicyString)
{
if (Enum.TryParse<ExecutionPolicies>(executionPolicyString, out var executionPolicyValue))
{
return executionPolicyValue;
}
else
{
throw new InvalidOperationException($"Found execution policy with value {executionPolicy}");
}
}
else
{
throw new InvalidOperationException($"Found execution policy of type {executionPolicy.GetType()}.");
}
}
private static RegistryKey GetOrCreateHomeKey()
{
var homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).OpenSubKey(RegistryKey, true);
if (homeRegistryKey is null)
{
homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).CreateSubKey(RegistryKey, true);
}
return homeRegistryKey;
}
}
}
@@ -0,0 +1,14 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System.Threading.Tasks;
namespace Daybreak.Services.Updater
{
public interface IApplicationUpdater : IApplicationLifetimeService
{
string CurrentVersion { get; }
void FinalizeUpdate();
Task<bool> UpdateAvailable();
Task<bool> DownloadUpdate(UpdateStatus updateStatus);
}
}
+97 -1
View File
@@ -1,13 +1,109 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
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);
[DllImport("kernel32.dll")]
public static extern bool QueryFullProcessImageName(IntPtr hProcess, uint dwFlags, StringBuilder lpExeName, ref uint lpdwSize);
}
}
}
+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();
}
}
}
+21
View File
@@ -0,0 +1,21 @@
<UserControl x:Class="Daybreak.Views.AskUpdateView"
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"
d:DesignHeight="450" d:DesignWidth="800">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="400" Height="200"
Background="White">
<TextBlock Text="An update has been detected. Do you wish to update?" FontSize="20" TextWrapping="Wrap"
Foreground="Black"></TextBlock>
<controls:OpaqueButton Text="No" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
FontSize="16" HorizontalAlignment="Center" Margin="0, 0, 80, 0"
Clicked="NoButton_Clicked"></controls:OpaqueButton>
<controls:OpaqueButton Text="Yes" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
FontSize="16" HorizontalAlignment="Center" Margin="80, 0, 0, 0"
Clicked="YesButton_Clicked"></controls:OpaqueButton>
</Grid>
</UserControl>
+39
View File
@@ -0,0 +1,39 @@
using Daybreak.Services.Logging;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using System.Extensions;
using System.Windows.Controls;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for AskUpdateView.xaml
/// </summary>
public partial class AskUpdateView : UserControl
{
private readonly ILogger logger;
private readonly IViewManager viewManager;
public AskUpdateView(
ILogger logger,
IViewManager viewManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
}
private void NoButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User declined update");
this.viewManager.ShowView<MainView>();
}
private void YesButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User accepted update");
this.viewManager.ShowView<UpdateView>();
}
}
}
+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,103 @@
<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="Stretch" Margin="0, 15, 0, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Multi-launch support" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0" Height="30"></TextBlock>
<TextBlock Text="GWToolbox startup delay (in ms)" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0" Height="30"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<ToggleButton IsChecked="{Binding ElementName=_this, Path=MultiLaunch, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
<TextBox Background="Transparent" Foreground="White" FontSize="16" Height="30"
PreviewTextInput="TextBox_AllowNumbersOnly" Text="{Binding ElementName=_this, Path=GWToolboxLaunchDelay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
CommandManager.PreviewCanExecute="TextBox_DisallowPaste"></TextBox>
</StackPanel>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,91 @@
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using System;
using System.Extensions;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
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));
public static readonly DependencyProperty GWToolboxLaunchDelayProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, string>(nameof(GWToolboxLaunchDelay));
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
set => this.SetValue(MultiLaunchProperty, value);
}
public string GWToolboxLaunchDelay
{
get => this.GetTypedValue<string>(GWToolboxLaunchDelayProperty);
set => this.SetValue(GWToolboxLaunchDelayProperty, 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;
this.GWToolboxLaunchDelay = config.ExperimentalFeatures.ToolboxAutoLaunchDelay.ToString();
}
private void SaveExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
config.ExperimentalFeatures.MultiLaunchSupport = this.MultiLaunch;
if (int.TryParse(this.GWToolboxLaunchDelay, out var gwToolboxLaunchDelay))
{
config.ExperimentalFeatures.ToolboxAutoLaunchDelay = gwToolboxLaunchDelay;
}
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>();
}
private void TextBox_AllowNumbersOnly(object sender, TextCompositionEventArgs e)
{
e.Handled = e.Text.Select(c => char.IsDigit(c)).All(result => result is true) is false;
}
private void TextBox_DisallowPaste(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Command == ApplicationCommands.ContextMenu || e.Command == ApplicationCommands.Paste)
{
e.CanExecute = false;
e.Handled = true;
}
}
}
}
@@ -1,4 +1,4 @@
<UserControl x:Class="Daybreak.Views.StartupView"
<UserControl x:Class="Daybreak.Views.MainView"
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"
@@ -25,9 +25,17 @@
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:OpaqueButton Text="Launch game" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="36" Width="250" Height="60" VerticalAlignment="Bottom" Margin="0, 0, 0, 30"
Foreground="White" FontSize="36" Width="250" Height="60" VerticalAlignment="Bottom"
IsEnabled="{Binding ElementName=_this, Path=LaunchButtonEnabled, Mode=OneWay}"
Clicked="OpaqueButton_Clicked" Grid.Row="1" Grid.ColumnSpan="3"></controls:OpaqueButton>
Clicked="LaunchButton_Clicked" Grid.Row="0" Grid.ColumnSpan="3"></controls:OpaqueButton>
<controls:OpaqueButton Text="Launch toolbox" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="24" Width="200" Height="40" Margin="0, 10, 0, 10"
IsEnabled="{Binding ElementName=_this, Path=LaunchToolboxButtonEnabled, Mode=OneWay}"
Clicked="LaunchToolboxButton_Clicked" Grid.Row="1" Grid.ColumnSpan="2"></controls:OpaqueButton>
<controls:OpaqueButton Text="Launch texmod" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="24" Width="200" Height="40" Margin="0, 10, 0, 10"
IsEnabled="{Binding ElementName=_this, Path=LaunchTexmodButtonEnabled, Mode=OneWay}"
Clicked="LaunchTexmodButton_Clicked" Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="1"></controls:OpaqueButton>
<Grid x:Name="RightContainer" Grid.Column="2" Margin="10">
<controls:ChromiumBrowserWrapper Address="{Binding ElementName=_this, Path=RightBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="RightBrowser_FavoriteUriChanged"
@@ -1,10 +1,12 @@
using Daybreak.Controls;
using Daybreak.Services.ApplicationDetection;
using Daybreak.Exceptions;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using System;
using System.Extensions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -14,20 +16,24 @@ namespace Daybreak.Views
/// <summary>
/// Interaction logic for StartupView.xaml
/// </summary>
public partial class StartupView : UserControl
public partial class MainView : UserControl
{
public static readonly DependencyProperty LaunchButtonEnabledProperty =
DependencyPropertyExtensions.Register<StartupView, bool>(nameof(LaunchButtonEnabled));
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchButtonEnabled));
public static readonly DependencyProperty LaunchToolboxButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchToolboxButtonEnabled));
public static readonly DependencyProperty LaunchTexmodButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchTexmodButtonEnabled));
public static readonly DependencyProperty RightBrowserAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(RightBrowserAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserAddress));
public static readonly DependencyProperty LeftBrowserAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(LeftBrowserAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserAddress));
public static readonly DependencyProperty RightBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(RightBrowserFavoriteAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserFavoriteAddress));
public static readonly DependencyProperty LeftBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(LeftBrowserFavoriteAddress));
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();
@@ -60,9 +66,19 @@ namespace Daybreak.Views
get => this.GetTypedValue<bool>(LaunchButtonEnabledProperty);
set => this.SetTypedValue(LaunchButtonEnabledProperty, value);
}
public bool LaunchToolboxButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchToolboxButtonEnabledProperty);
set => this.SetTypedValue(LaunchToolboxButtonEnabledProperty, value);
}
public bool LaunchTexmodButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchTexmodButtonEnabledProperty);
set => this.SetTypedValue(LaunchTexmodButtonEnabledProperty, value);
}
public StartupView(
IApplicationDetector applicationDetector,
public MainView(
IApplicationLauncher applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager)
{
@@ -90,14 +106,9 @@ namespace Daybreak.Views
private void CheckGameState()
{
if (applicationDetector.IsGuildwarsRunning)
{
this.LaunchButtonEnabled = false;
}
else
{
this.LaunchButtonEnabled = true;
}
this.LaunchButtonEnabled = this.applicationDetector.IsGuildwarsRunning is false;
this.LaunchToolboxButtonEnabled = this.applicationDetector.IsToolboxRunning is false;
this.LaunchTexmodButtonEnabled = this.applicationDetector.IsTexmodRunning is false;
}
private void StartupView_Loaded(object sender, RoutedEventArgs e)
@@ -109,11 +120,45 @@ namespace Daybreak.Views
this.cancellationTokenSource.Cancel();
}
private void OpaqueButton_Clicked(object sender, EventArgs e)
private async void LaunchButton_Clicked(object sender, EventArgs e)
{
try
{
this.applicationDetector.LaunchGuildwars();
await this.applicationDetector.LaunchGuildwars();
if (this.configurationManager.GetConfiguration().ToolboxAutoLaunch is true)
{
var delay = this.configurationManager.GetConfiguration().ExperimentalFeatures.ToolboxAutoLaunchDelay;
await Task.Delay(delay);
await this.applicationDetector.LaunchGuildwarsToolbox();
}
}
catch (CredentialsNotFoundException)
{
this.viewManager.ShowView<AccountsView>();
}
catch (ExecutableNotFoundException)
{
this.viewManager.ShowView<ExecutablesView>();
}
}
private void LaunchToolboxButton_Clicked(object sender, EventArgs e)
{
try
{
this.applicationDetector.LaunchGuildwarsToolbox();
}
catch
{
this.viewManager.ShowView<SettingsView>();
}
}
private void LaunchTexmodButton_Clicked(object sender, EventArgs e)
{
try
{
this.applicationDetector.LaunchTexmod();
}
catch
{
+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 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 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>
<controls:TileButton 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 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>
</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>();
}
}
}
+98 -23
View File
@@ -8,40 +8,115 @@
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>
<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="FilePickerGlyph_Clicked"></controls:FilePickerGlyph>
<TextBlock Text="Address bar readonly: " FontSize="22" Foreground="White" Grid.Row="5"/>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="5"
x:Name="AddressBarReadonlyTextbox" FontSize="22" Background="Transparent" Foreground="White"></TextBox>
<StackPanel Orientation="Vertical" Grid.Row="1">
<TextBlock Text="Texmod path" FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="GWToolbox path: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="GWToolbox auto-launch: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="Address bar readonly: " FontSize="22" Foreground="White"/>
<TextBlock Text="Left browser homepage: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="Right browser homepage: " FontSize="22" Foreground="White"></TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<Grid>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=TexmodPath, Mode=TwoWay}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="TexmodFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<Grid>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=ToolboxPath, Mode=TwoWay}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ToolboxFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Background="Transparent" Foreground="White" IsChecked="{Binding ElementName=_this, Path=ToolboxAutoLaunch, Mode=TwoWay}"></ToggleButton>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Background="Transparent" Foreground="White" IsChecked="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=TwoWay}"></ToggleButton>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=LeftBrowserUrl, Mode=TwoWay}"
TextChanged="LeftBrowserUrl_TextChanged"></TextBox>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=RightBrowserUrl, Mode=TwoWay}"
TextChanged="RightBrowserUrl_TextChanged"></TextBox>
</StackPanel>
</Grid>
</UserControl>
+91 -31
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,58 @@ namespace Daybreak.Views
/// </summary>
public partial class SettingsView : UserControl
{
public static readonly DependencyProperty TexmodPathProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(TexmodPath));
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));
public static readonly DependencyProperty ToolboxAutoLaunchProperty =
DependencyPropertyExtensions.Register<SettingsView, bool>(nameof(ToolboxAutoLaunch));
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
private readonly IViewManager viewManager;
public string TexmodPath
{
get => this.GetTypedValue<string>(TexmodPathProperty);
set => this.SetValue(TexmodPathProperty, value);
}
public bool ToolboxAutoLaunch
{
get => this.GetTypedValue<bool>(ToolboxAutoLaunchProperty);
set => this.SetValue(ToolboxAutoLaunchProperty, value);
}
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,35 +74,28 @@ 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.AddressBarReadonly = config.AddressBarReadonly;
this.ToolboxPath = config.ToolboxPath;
this.LeftBrowserUrl = config.LeftBrowserDefault;
this.RightBrowserUrl = config.RightBrowserDefault;
this.ToolboxAutoLaunch = config.ToolboxAutoLaunch;
this.TexmodPath = config.TexmodPath;
}
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;
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;
currentConfig.ToolboxAutoLaunch = this.ToolboxAutoLaunch;
currentConfig.TexmodPath = this.TexmodPath;
this.configurationManager.SaveConfiguration(currentConfig);
this.credentialManager.StoreCredentials(new LoginCredentials { Username = this.UsernameTextbox.Text, Password = this.PasswordBox.Password });
this.viewManager.ShowView<StartupView>();
this.viewManager.ShowView<SettingsCategoryView>();
}
private void FilePickerGlyph_Clicked(object sender, System.EventArgs e)
private void ToolboxFilePickerGlyph_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
{
@@ -71,13 +106,38 @@ namespace Daybreak.Views
};
if (filePicker.ShowDialog() is true)
{
this.GamePathTextbox.Text = filePicker.FileName;
this.ToolboxPath = filePicker.FileName;
}
}
private void BackButton_Clicked(object sender, System.EventArgs e)
private void TexmodFilePickerGlyph_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<StartupView>();
var filePicker = new OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "exe",
Multiselect = false
};
if (filePicker.ShowDialog() is true)
{
this.TexmodPath = filePicker.FileName;
}
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void LeftBrowserUrl_TextChanged(object sender, TextChangedEventArgs e)
{
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 @@
<UserControl x:Class="Daybreak.Views.UpdateView"
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"
x:Name="_this"
xmlns:controls="clr-namespace:Daybreak.Controls"
Loaded="UpdateView_Loaded"
d:DesignHeight="450" d:DesignWidth="800">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Background="White" MinHeight="200" MinWidth="400">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 10" Foreground="Black"
TextWrapping="Wrap"></TextBlock>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
<controls:OpaqueButton Text="Ok" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="30" Height="20"
IsEnabled="{Binding ElementName=_this, Path=ContinueButtonEnabled, Mode=OneWay}"
Clicked="OpaqueButton_Clicked" Foreground="Black"></controls:OpaqueButton>
</StackPanel>
</Grid>
</UserControl>
+102
View File
@@ -0,0 +1,102 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for UpdateView.xaml
/// </summary>
public partial class UpdateView : UserControl
{
public readonly static DependencyProperty DescriptionProperty =
DependencyPropertyExtensions.Register<UpdateView, string>(nameof(Description));
public readonly static DependencyProperty ProgressValueProperty =
DependencyPropertyExtensions.Register<UpdateView, double>(nameof(ProgressValue));
public readonly static DependencyProperty ContinueButtonEnabledProperty =
DependencyPropertyExtensions.Register<UpdateView, bool>(nameof(ContinueButtonEnabled));
private readonly ILogger logger;
private readonly IViewManager viewManager;
private readonly IApplicationUpdater applicationUpdater;
private readonly UpdateStatus updateStatus = new();
private bool success = false;
public string Description
{
get => this.GetTypedValue<string>(DescriptionProperty);
set => this.SetValue(DescriptionProperty, value);
}
public double ProgressValue
{
get => this.GetTypedValue<double>(ProgressValueProperty);
set => this.SetValue(ProgressValueProperty, value);
}
public bool ContinueButtonEnabled
{
get => this.GetTypedValue<bool>(ContinueButtonEnabledProperty);
set => this.SetValue(ContinueButtonEnabledProperty, value);
}
public UpdateView(
IApplicationUpdater applicationUpdater,
ILogger logger,
IViewManager viewManager)
{
this.applicationUpdater = applicationUpdater;
this.logger = logger;
this.viewManager = viewManager;
this.updateStatus.PropertyChanged += UpdateStatus_PropertyChanged;
this.InitializeComponent();
}
private void UpdateStatus_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
if (this.updateStatus.CurrentStep is UpdateStatus.DownloadUpdateStep downloadUpdateStep)
{
this.ProgressValue = downloadUpdateStep.Progress * 100;
}
this.Description = this.updateStatus.CurrentStep.Name;
});
}
private async void UpdateView_Loaded(object sender, RoutedEventArgs e)
{
this.logger.LogInformation("Starting update procedure");
var success = await applicationUpdater.DownloadUpdate(updateStatus).ConfigureAwait(true);
if (success is false)
{
this.logger.LogError("Update procedure failed");
}
else
{
this.success= true;
this.logger.LogInformation("Downloaded update");
}
this.ContinueButtonEnabled = true;
}
private void OpaqueButton_Clicked(object sender, System.EventArgs e)
{
if (this.success)
{
this.applicationUpdater.FinalizeUpdate();
Application.Current.Shutdown();
}
else
{
this.viewManager.ShowView<MainView>();
}
}
}
}
+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>
+31 -4
View File
@@ -1,13 +1,19 @@
# 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)
![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
# Features
Automatically detect if guildwars is running or not. Includes the ability to launch guildwars from the client.
Manages username and password combination.
Multibox support.
Manages multiple username and password combinations.
Manages multiple executables.
Ability to set a character name which gets autoloaded during launch.
@@ -16,3 +22,24 @@ Embedded browser set on useful pages or links.
Ability to set default page for each of the two browser windows.
Rotates screenshots from "Screenshots" folder. If no screenshots are present in the folder, downloads and rotates images from http://bloogum.net/guildwars (link to page is visible when showing images from the website).
# Examples/Usage
To modify any settings, press the settings button on the titlebar
![Settings button](https://i.imgur.com/0QSTvNF.png)
To adjust functionality, choose one of the settings categories
![Settings categories](https://i.imgur.com/LtPDvHY.png)
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. Preffered page can also be selected from settings.
![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.
To adjust accounts, go into account settings. Clicking on the star next to an account sets it as the current account.
![Account settings](https://i.imgur.com/Pwycwwr.png)
To adjust executables, go into Guildwars settings. Clicking on the star next to the executable path sets it as the current executable.
![Guildwars settings](https://i.imgur.com/XChX19t.png)
To enable multiboxing (multi-launch), go into Experimental settings. Then, switch between executables/accounts and launch them.
![Multibox toggle](https://i.imgur.com/vEFF2pb.png)