Compare commits

...
8 Commits
Author SHA1 Message Date
amacocianandGitHub 3ee58e2711 Release 0.9.9.8 (#545)
* Synchronize browser history

* Intercept mouse button navigation
Closes #535
Closes #544
2024-01-26 14:24:32 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
9b3b083aec Bump MSTest.TestAdapter from 3.1.1 to 3.2.0 (#542)
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v.3.2.0)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2024-01-26 13:09:38 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ecb9b8f172 Bump MSTest.TestFramework from 3.1.1 to 3.2.0 (#543)
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v.3.2.0)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-26 13:08:22 +01:00
amacocianandGitHub 5765c27240 Release 0.9.9.7 (#541)
* Introduce support for browser extensions

* Setup ublock origin

* Release 0.9.9.7 branch

* Automatically backup settings
Closes #539

* Compare settings with remote
2024-01-23 18:25:31 +00:00
amacocianandGitHub c53456112a Update GWCA (#538)
Fix inventory bug
2024-01-20 10:19:46 +00:00
amacocianandGitHub ccea21886e Release 0.9.9.5 (#531)
* Remove character name from credentials
Update vulnerable packages

* Update SharpCompress
2024-01-17 20:00:12 +00:00
amacocianandGitHub 37be8de5ac Update httplib (#529)
Fix missing data when player has no titles
Fix http requests stalling
2024-01-15 19:20:55 +00:00
amacocianandGitHub 107e43a15f Update GWCA (#528) 2024-01-15 13:34:41 +00:00
41 changed files with 9028 additions and 8411 deletions
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpCompress" Version="0.35.0" />
<PackageReference Include="SharpCompress" Version="0.36.0" />
</ItemGroup>
</Project>
+17 -4
View File
@@ -2,17 +2,28 @@
# project specific logic here.
#
project(Daybreak.GWCA)
project(Daybreak.GWCA VERSION 1.0.0)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "You are configuring a 64bit build, this is not supported. Run cmake with `-A Win32`")
endif()
set(VERSION_MAJOR 0)
set(VERSION_MINOR 9)
set(VERSION_PATCH 9)
set(VERSION_TWEAK 6)
set(VERSION_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in" "${VERSION_RC}" @ONLY)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(Daybreak.GWCA_FOLDER "${CMAKE_CURRENT_LIST_DIR}/")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/bin/install)
add_compile_options(/MP /permissive-)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/")
add_library(Daybreak.GWCA SHARED)
@@ -23,11 +34,13 @@ file(GLOB SOURCES
"header/*.h*"
"header/*/*.h")
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${SOURCES})
target_sources(Daybreak.GWCA PRIVATE ${SOURCES})
source_group("Generated Files" FILES ${VERSION_RC})
target_sources(Daybreak.GWCA PRIVATE ${SOURCES} ${VERSION_RC})
target_compile_features(Daybreak.GWCA PUBLIC cxx_std_17)
target_compile_definitions(Daybreak.GWCA PRIVATE
$<$<CONFIG:Debug>:BUILD_TYPE_DEBUG>
$<$<CONFIG:Release>:BUILD_TYPE_RELEASE>
NOMINMAX
)
target_precompile_headers(Daybreak.GWCA PRIVATE "header/pch.h")
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -287,6 +287,11 @@ namespace Daybreak::Modules::GameModule {
auto name = Daybreak::Utils::WStringToString(nameString);
player.Name = name;
if (titles.empty() ||
titleTiers.empty()) {
return;
}
for (const auto &t : titles) {
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
gwTitle = t;
@@ -360,14 +365,8 @@ namespace Daybreak::Modules::GameModule {
}
auto titles = GetTitles();
if (titles.empty()) {
return gamePayload;
}
auto titleTiers = GetTitleTiers();
if (titleTiers.empty()) {
return gamePayload;
}
auto players = GetPlayers();
if (players.empty()) {
+5 -6
View File
@@ -287,6 +287,11 @@ namespace Daybreak::Modules::MainPlayerModule {
auto name = Daybreak::Utils::WStringToString(gwPlayerName);
player.Name = name;
if (titles.empty() ||
titleTiers.empty()) {
return;
}
for (const auto &t : titles) {
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
gwTitle = t;
@@ -358,14 +363,8 @@ namespace Daybreak::Modules::MainPlayerModule {
}
auto titles = GetTitles();
if (titles.empty()) {
return gamePayload;
}
auto titleTiers = GetTitleTiers();
if (titleTiers.empty()) {
return gamePayload;
}
auto players = GetPlayers();
if (players.empty()) {
+3
View File
@@ -62,6 +62,9 @@ namespace http {
int port = FindPort();
if (port > 0) {
std::cout << "Initializing server on port " << port << std::endl;
server.set_read_timeout(1, 0);
server.set_write_timeout(1, 0);
server.set_idle_interval(0, 100000);
server.listen("0.0.0.0", port);
return true;
}
+34
View File
@@ -0,0 +1,34 @@
#include <winver.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,@VERSION_TWEAK@
PRODUCTVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,@VERSION_TWEAK@
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "gwdevhub"
VALUE "FileDescription", "Daybreak.GWCA"
VALUE "FileVersion", "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.@VERSION_TWEAK@"
VALUE "InternalName", "Daybreak.GWCA"
VALUE "LegalCopyright", ""
VALUE "OriginalFilename", "Daybreak.GWCA.dll"
VALUE "ProductName", "Daybreak.GWCA"
VALUE "ProductVersion", "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.@VERSION_TWEAK@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 0x4B0
END
END
+2 -2
View File
@@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -1,5 +1,6 @@
using Daybreak.Attributes;
using Daybreak.Configuration.FocusView;
using Daybreak.Models.Browser;
using Newtonsoft.Json;
namespace Daybreak.Configuration.Options;
@@ -49,7 +50,7 @@ public sealed class FocusViewOptions
[OptionName(Name = "Energy Display Mode", Description = "Sets how should the energy display show the information")]
public PointsDisplay EnergyDisplay { get; set; }
[JsonProperty(nameof(BrowserUrl))]
[JsonProperty(nameof(BrowserHistory))]
[OptionIgnore]
public string? BrowserUrl { get; set; } = "https://wiki.guildwars.com/wiki/Main_Page";
public BrowserHistory BrowserHistory { get; set; } = new();
}
@@ -56,4 +56,8 @@ public sealed class LauncherOptions
[JsonProperty(nameof(BetaUpdate))]
[OptionName(Name = "Beta Update", Description = "If true, the launcher will use the new update procedure")]
public bool BetaUpdate { get; set; } = true;
[JsonProperty(nameof(AutoBackupSettings))]
[OptionName(Name = "Auto Backup Settings", Description = "If true, the launcher will attempt to backup settings periodically")]
public bool AutoBackupSettings { get; set; } = false;
}
+21 -6
View File
@@ -82,6 +82,8 @@ using Daybreak.Services.DirectSong;
using Daybreak.Views.Onboarding.DirectSong;
using Daybreak.Services.SevenZip;
using Daybreak.Services.ReShade.Notifications;
using Daybreak.Services.UBlockOrigin;
using Daybreak.Services.Browser;
namespace Daybreak.Configuration;
@@ -155,6 +157,10 @@ public class ProjectConfiguration : PluginConfigurationBase
.RegisterHttpClient<UModService>()
.WithMessageHandler(this.SetupLoggingAndMetrics<UModService>)
.WithDefaultRequestHeadersSetup(this.SetupDaybreakUserAgent)
.Build()
.RegisterHttpClient<UBlockOriginService>()
.WithMessageHandler(this.SetupLoggingAndMetrics<UBlockOriginService>)
.WithDefaultRequestHeadersSetup(this.SetupDaybreakUserAgent)
.Build();
}
@@ -190,19 +196,19 @@ public class ProjectConfiguration : PluginConfigurationBase
services.AddSingleton<IPostUpdateActionProducer>(sp => sp.GetRequiredService<PostUpdateActionManager>());
services.AddSingleton<IPostUpdateActionProvider>(sp => sp.GetRequiredService<PostUpdateActionManager>());
services.AddSingleton<IMenuService, MenuService>();
services.AddSingleton<IMenuServiceInitializer, MenuService>(sp => sp.GetRequiredService<IMenuService>().As<MenuService>()!);
services.AddSingleton<IMenuServiceInitializer, MenuService>(sp => sp.GetRequiredService<IMenuService>().Cast<MenuService>());
services.AddSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
services.AddSingleton<IMutexHandler, MutexHandler>();
services.AddSingleton<IShortcutManager, ShortcutManager>();
services.AddSingleton<IMetricsService, MetricsService>();
services.AddSingleton<IStartupActionProducer, StartupActionManager>();
services.AddSingleton<IOptionsProducer, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
services.AddSingleton<IOptionsUpdateHook, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
services.AddSingleton<IOptionsProvider, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
services.AddSingleton<IOptionsProducer, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
services.AddSingleton<IOptionsUpdateHook, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
services.AddSingleton<IOptionsProvider, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
services.AddSingleton<IThemeManager, ThemeManager>();
services.AddSingleton<INotificationService, NotificationService>();
services.AddSingleton<INotificationProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
services.AddSingleton<INotificationHandlerProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
services.AddSingleton<INotificationProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().Cast<NotificationService>());
services.AddSingleton<INotificationHandlerProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().Cast<NotificationService>());
services.AddSingleton<ILiveChartInitializer, LiveChartInitializer>();
services.AddSingleton<IImageCache, ImageCache>();
services.AddSingleton<ISoundService, SoundService>();
@@ -220,6 +226,8 @@ public class ProjectConfiguration : PluginConfigurationBase
services.AddSingleton<ISevenZipExtractor, SevenZipExtractor>();
services.AddSingleton<IGraphClient, GraphClient>();
services.AddSingleton<IOptionsSynchronizationService, OptionsSynchronizationService>();
services.AddScoped<IBrowserExtensionsManager, BrowserExtensionsManager>();
services.AddScoped<IBrowserExtensionsProducer, BrowserExtensionsManager>(sp => sp.GetRequiredService<IBrowserExtensionsManager>().Cast<BrowserExtensionsManager>());
services.AddScoped<ICredentialManager, CredentialManager>();
services.AddScoped<IApplicationLauncher, ApplicationLauncher>();
services.AddScoped<IScreenshotProvider, ScreenshotProvider>();
@@ -253,6 +261,7 @@ public class ProjectConfiguration : PluginConfigurationBase
services.AddScoped<IToolboxClient, ToolboxClient>();
services.AddScoped<IProcessInjector, ProcessInjector>();
services.AddScoped<ILaunchConfigurationService, LaunchConfigurationService>();
services.AddScoped<IBrowserHistoryManager, BrowserHistoryManager>();
}
public override void RegisterViews(IViewProducer viewProducer)
@@ -446,6 +455,12 @@ public class ProjectConfiguration : PluginConfigurationBase
modsManager.RegisterMod<IDirectSongService, DirectSongService>(singleton: true);
}
public override void RegisterBrowserExtensions(IBrowserExtensionsProducer browserExtensionsProducer)
{
browserExtensionsProducer.ThrowIfNull();
browserExtensionsProducer.RegisterExtension<UBlockOriginService>();
}
private void RegisterLiteCollections(IServiceCollection services)
{
this.RegisterLiteCollection<Models.Log, LoggingOptions>(services);
@@ -10,6 +10,7 @@
mc:Ignorable="d"
Loaded="UserControl_Loaded"
Unloaded="UserControl_Unloaded"
PreviewMouseDown="Browser_PreviewMouseDown"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
@@ -51,11 +52,11 @@
<WrapPanel>
<buttons:BackButton Height="30" Width="30"
Clicked="BackButton_Clicked" Margin="5" Cursor="Hand"
IsEnabled="{Binding ElementName=WebBrowser, Path=CanGoBack, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserHistoryManager.CanGoBack, Mode=OneWay}"
ToolTip="Go back"/>
<buttons:BackButton Height="30" Width="30"
Clicked="ForwardButton_Clicked" Margin="5" Cursor="Hand"
IsEnabled="{Binding ElementName=WebBrowser, Path=CanGoForward, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserHistoryManager.CanGoForward, Mode=OneWay}"
ToolTip="Go forward">
<buttons:BackButton.RenderTransform>
<RotateTransform Angle="180" CenterX="15" CenterY="15"></RotateTransform>
@@ -3,12 +3,14 @@ using Daybreak.Configuration.Options;
using Daybreak.Models;
using Daybreak.Models.Browser;
using Daybreak.Models.Guildwars;
using Daybreak.Services.Browser;
using Daybreak.Services.BuildTemplates;
using Daybreak.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
@@ -21,6 +23,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Input;
namespace Daybreak.Controls;
@@ -39,10 +42,10 @@ public partial class ChromiumBrowserWrapper : UserControl
private const string BrowserSearchLink = $"https://www.google.com/search?q={BrowserSearchPlaceholder}";
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
private static readonly Regex WebAddressRegex = new("^((http|ftp|https)://)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?", RegexOptions.Compiled);
private static readonly object Lock = new();
private static readonly Regex WebAddressRegex = BuildWebAddressRegex();
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
private static CoreWebView2Environment? CoreWebView2Environment;
private static CoreWebView2Environment? CoreWebView2Environment;
public event EventHandler<string>? FavoriteUriChanged;
public event EventHandler? MaximizeClicked;
@@ -50,12 +53,16 @@ public partial class ChromiumBrowserWrapper : UserControl
public event EventHandler<DownloadPayload>? DownloadingFile;
public event EventHandler<string>? DownloadedFile;
private readonly HashSet<ulong> domNavigationIds = [];
private readonly Task initializationTask;
private readonly IHttpClient<ChromiumBrowserWrapper> httpClient;
private readonly ILiveOptions<BrowserOptions> liveOptions;
private readonly ILogger<ChromiumBrowserWrapper> logger;
private readonly IBuildTemplateManager buildTemplateManager;
private readonly IBrowserHistoryManager historyManager;
private readonly IBrowserExtensionsManager browserExtensionsManager;
[GenerateDependencyProperty(InitialValue = true)]
private bool canDownloadBuild;
[GenerateDependencyProperty(InitialValue = true)]
@@ -95,8 +102,13 @@ public partial class ChromiumBrowserWrapper : UserControl
set => this.SetValue(AddressProperty, value);
}
public IBrowserHistoryManager BrowserHistoryManager => this.historyManager;
public ChromiumBrowserWrapper()
: this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IHttpClient<ChromiumBrowserWrapper>>(),
: this(
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBrowserHistoryManager>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBrowserExtensionsManager>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IHttpClient<ChromiumBrowserWrapper>>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveOptions<BrowserOptions>>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILogger<ChromiumBrowserWrapper>>())
@@ -104,18 +116,22 @@ public partial class ChromiumBrowserWrapper : UserControl
}
public ChromiumBrowserWrapper(
IBrowserHistoryManager historyManager,
IBrowserExtensionsManager browserExtensionsManager,
IHttpClient<ChromiumBrowserWrapper> httpClient,
ILiveOptions<BrowserOptions> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.historyManager = historyManager.ThrowIfNull();
this.browserExtensionsManager = browserExtensionsManager.ThrowIfNull();
this.httpClient = httpClient.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.InitializeEnvironment();
this.InitializeComponent();
this.historyManager.InitializeHistoryManager(this);
this.initializationTask = Task.Run(this.InitializeBrowserSafe);
}
@@ -149,24 +165,22 @@ public partial class ChromiumBrowserWrapper : UserControl
{
try
{
lock (Lock)
if (CoreWebView2Environment is not null)
{
if (CoreWebView2Environment is not null)
{
this.BrowserSupported = true;
return;
}
CoreWebView2Environment ??= System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", new CoreWebView2EnvironmentOptions
{
EnableTrackingPrevention = true,
AllowSingleSignOnUsingOSPrimaryAccount = true
}));
this.BrowserSupported = true;
return;
}
CoreWebView2Environment ??= System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", new CoreWebView2EnvironmentOptions
{
EnableTrackingPrevention = true,
AllowSingleSignOnUsingOSPrimaryAccount = true,
AreBrowserExtensionsEnabled = true,
}));
this.BrowserSupported = true;
}
catch(Exception e)
catch (Exception e)
{
this.logger!.LogWarning($"Browser initialization failed. Details: {e}");
this.BrowserSupported = false;
@@ -177,10 +191,7 @@ public partial class ChromiumBrowserWrapper : UserControl
{
await this.Dispatcher.InvokeAsync(async () =>
{
while (!Monitor.TryEnter(Lock))
{
await Task.Delay(100);
}
await SemaphoreSlim.WaitAsync();
try
{
@@ -189,7 +200,7 @@ public partial class ChromiumBrowserWrapper : UserControl
}
finally
{
Monitor.Exit(Lock);
SemaphoreSlim.Release();
}
});
}
@@ -201,12 +212,18 @@ public partial class ChromiumBrowserWrapper : UserControl
if (!this.BrowserSupported ||
!this.BrowserEnabled)
{
return;
}
if (this.WebBrowser.CoreWebView2 is not null)
{
return;
}
this.ShowBrowserDisabledMessage = false;
await this.WebBrowser.EnsureCoreWebView2Async(CoreWebView2Environment);
await this.WebBrowser.EnsureCoreWebView2Async(CoreWebView2Environment).ConfigureAwait(true);
await this.browserExtensionsManager.InitializeBrowserEnvironment(this.WebBrowser.CoreWebView2!.Profile, CoreWebView2Environment!.BrowserVersionString).ConfigureAwait(true);
if (this.Address is not null &&
Uri.TryCreate(this.Address, UriKind.RelativeOrAbsolute, out var uri))
{
@@ -217,6 +234,21 @@ public partial class ChromiumBrowserWrapper : UserControl
this.AddressBarReadonly = this.liveOptions!.Value.AddressBarReadonly;
this.CanDownloadBuild = this.liveOptions.Value.DynamicBuildLoading;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.KeyDown += (sender, e) =>
{
if ((e.Key == Key.Left && Keyboard.Modifiers == ModifierKeys.Alt) ||
(e.Key == Key.BrowserBack))
{
this.BrowserHistoryManager.GoBack();
e.Handled = true; // Prevent default behavior
}
else if ((e.Key == Key.Right && Keyboard.Modifiers == ModifierKeys.Alt) ||
(e.Key == Key.BrowserForward))
{
this.BrowserHistoryManager.GoForward();
e.Handled = true; // Prevent default behavior
}
};
this.WebBrowser.NavigationStarting += (browser, args) =>
{
if (this.CanNavigate is false && args.Uri != this.Address)
@@ -271,6 +303,13 @@ public partial class ChromiumBrowserWrapper : UserControl
};
this.WebBrowser.CoreWebView2.DOMContentLoaded += async (browser, args) =>
{
if (this.domNavigationIds.Contains(args.NavigationId))
{
return;
}
this.domNavigationIds.Add(args.NavigationId);
await this.WebBrowser.CoreWebView2.ExecuteScriptAsync(Scripts.CaptureNavigationButtons);
if (this.CanDownloadBuild)
{
await this.WebBrowser.CoreWebView2.ExecuteScriptAsync(Scripts.SendSelectionOnContextMenu);
@@ -376,12 +415,12 @@ public partial class ChromiumBrowserWrapper : UserControl
{
payload = args.WebMessageAsJson.Deserialize<BrowserPayload>();
}
catch(Exception e)
catch (Exception e)
{
this.logger!.LogError(e, $"Exception encountered when deserializing {nameof(BrowserPayload)}");
}
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
if (payload?.Key is BrowserPayload.PayloadKeys.ContextMenu)
{
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
var maybeTemplate = contextMenuPayload?.Value?.Selection?.Trim();
@@ -411,12 +450,20 @@ public partial class ChromiumBrowserWrapper : UserControl
this.ContextMenu.IsOpen = true;
});
}
catch(Exception e)
catch (Exception e)
{
this.logger!.LogWarning($"Exception when decoding template {maybeTemplate}. Details {e}");
}
});
}
else if (payload?.Key is BrowserPayload.PayloadKeys.XButton1Pressed)
{
this.BrowserHistoryManager.GoBack();
}
else if (payload?.Key is BrowserPayload.PayloadKeys.XButton2Pressed)
{
this.BrowserHistoryManager.GoForward();
}
}
private void DownloadOperation_StateChanged(object? sender, object? e)
@@ -461,26 +508,24 @@ public partial class ChromiumBrowserWrapper : UserControl
return;
}
this.historyManager.UnInitializeHistoryManager();
this.WebBrowser?.Dispose();
this.browserInitialized = false;
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.WebBrowser.GoBack();
this.Address = this.WebBrowser.Source.ToString();
this.historyManager.GoBack();
}
private void ForwardButton_Clicked(object sender, EventArgs e)
{
this.WebBrowser.GoForward();
this.Address = this.WebBrowser.Source.ToString();
this.historyManager.GoForward();
}
private void RefreshGlyph_Clicked(object sender, EventArgs e)
{
this.WebBrowser.Reload();
this.Address = this.WebBrowser.Source.ToString();
this.BrowserHistoryManager.Reload();
}
private void CancelGlyph_Clicked(object sender, EventArgs e)
@@ -504,6 +549,11 @@ public partial class ChromiumBrowserWrapper : UserControl
this.MaximizeClicked?.Invoke(this, e);
}
private void Browser_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
}
private void CheckFavoriteAddress()
{
if (this.Address == this.FavoriteAddress)
@@ -541,4 +591,7 @@ public partial class ChromiumBrowserWrapper : UserControl
Uri.TryCreate(address, UriKind.Absolute, out var uri);
return uri?.ToString() ?? string.Empty;
}
[GeneratedRegex("^((http|ftp|https)://)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?", RegexOptions.Compiled)]
private static partial Regex BuildWebAddressRegex();
}
@@ -23,7 +23,6 @@
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
@@ -31,7 +30,6 @@
</Grid.ColumnDefinitions>
<TextBlock FontSize="16" Text="Username:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
<TextBlock FontSize="16" Text="Password:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
<TextBlock FontSize="16" Text="Character name:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
<TextBox Text="{Binding ElementName=_this, Path=Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Background="Transparent"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
FontSize="16" TextChanged="UsernameTextbox_TextChanged" Margin="5"
@@ -40,10 +38,6 @@
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="1"
FontSize="16" PasswordChanged="Passwordbox_PasswordChanged" Margin="5"
ToolTip="Password" />
<TextBox Text="{Binding ElementName=_this, Path=CharacterName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Background="Transparent"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="1"
FontSize="16" TextChanged="CharacterNameTextbox_TextChanged" Margin="5"
ToolTip="Character name" />
</Grid>
<buttons:BinButton Grid.Column="2" Height="30" Width="30" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" VerticalAlignment="Center" Margin="5, 5, 5, 5"
Clicked="BinButton_Clicked"
@@ -21,8 +21,6 @@ public partial class AccountTemplate : UserControl
private string username = string.Empty;
[GenerateDependencyProperty]
private string password = string.Empty;
[GenerateDependencyProperty]
private string characterName = string.Empty;
public AccountTemplate()
{
@@ -36,7 +34,6 @@ public partial class AccountTemplate : UserControl
{
this.PasswordBox.Password = loginCredentials.Password;
this.Username = loginCredentials.Username;
this.CharacterName = loginCredentials.CharacterName;
}
}
@@ -50,11 +47,6 @@ public partial class AccountTemplate : UserControl
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;
@@ -33,9 +33,6 @@
Foreground="{StaticResource MahApps.Brushes.Accent}"
Visibility="{Binding ElementName=_this, Path=GameRunning, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</WrapPanel>
<TextBlock Text="{Binding Credentials.CharacterName}"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding ExecutablePath}"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="12"
@@ -52,9 +49,6 @@
FontSize="22"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
</WrapPanel>
<TextBlock Text="{Binding Credentials.CharacterName}"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding ExecutablePath}"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
FontSize="12"
+5 -3
View File
@@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.9.2</Version>
<Version>0.9.9.8</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -66,7 +66,7 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="..\build\Daybreak.GWCA\$(Configuration)\Daybreak.GWCA.dll" Link="GWCA\Daybreak.GWCA.dll">
<None Include="..\Daybreak.GWCA\bin\$(Configuration)\Daybreak.GWCA.dll" Link="GWCA\Daybreak.GWCA.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -89,7 +89,7 @@
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="DiffPlex" Version="1.7.2" />
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.55" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc2" />
@@ -108,8 +108,10 @@
<PackageReference Include="Slim" Version="1.9.2" />
<PackageReference Include="Svg" Version="3.4.6" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.2" />
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.3.1" />
+6 -1
View File
@@ -1,5 +1,6 @@
using Daybreak.Configuration;
using Daybreak.Models.Progress;
using Daybreak.Services.Browser;
using Daybreak.Services.Drawing;
using Daybreak.Services.ExceptionHandling;
using Daybreak.Services.Mods;
@@ -95,6 +96,7 @@ public sealed class Launcher : ExtendedApplication<MainWindow>
var drawingModuleProducer = this.ServiceProvider.GetRequiredService<IDrawingModuleProducer>();
var notificationHandlerProducer = this.ServiceProvider.GetRequiredService<INotificationHandlerProducer>();
var modsManager = this.ServiceProvider.GetRequiredService<IModsManager>();
var browserExtensionsProducer = this.ServiceProvider.GetRequiredService<IBrowserExtensionsProducer>();
startupStatus.CurrentStep = StartupStatus.Custom("Loading views");
this.projectConfiguration.RegisterViews(viewProducer);
@@ -108,6 +110,8 @@ public sealed class Launcher : ExtendedApplication<MainWindow>
this.projectConfiguration.RegisterNotificationHandlers(notificationHandlerProducer);
startupStatus.CurrentStep = StartupStatus.Custom("Loading mods");
this.projectConfiguration.RegisterMods(modsManager);
startupStatus.CurrentStep = StartupStatus.Custom("Loading browser extensions");
this.projectConfiguration.RegisterBrowserExtensions(browserExtensionsProducer);
this.logger = this.ServiceProvider.GetRequiredService<ILogger<Launcher>>();
this.exceptionHandler = this.ServiceProvider.GetRequiredService<IExceptionHandler>();
@@ -123,7 +127,8 @@ public sealed class Launcher : ExtendedApplication<MainWindow>
startupActionProducer,
drawingModuleProducer,
notificationHandlerProducer,
modsManager);
modsManager,
browserExtensionsProducer);
}
catch(Exception e)
{
+1 -1
View File
@@ -112,9 +112,9 @@ public partial class MainWindow : MetroWindow
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.splashScreenService.HideSplashScreen();
this.SetupImageCycle();
this.viewManager.ShowView<LauncherView>();
this.splashScreenService.HideSplashScreen();
}
private void SynchronizeButton_Click(object sender, EventArgs e)
@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Daybreak.Models.Browser;
public sealed class BrowserHistory
{
public List<string> History { get; set; } = [];
public int CurrentPosition { get; set; } = -1;
}
+3 -1
View File
@@ -9,7 +9,9 @@ public class BrowserPayload
public enum PayloadKeys
{
None,
ContextMenu
ContextMenu,
XButton1Pressed,
XButton2Pressed
}
[JsonProperty(nameof(Key))]
+2 -4
View File
@@ -7,14 +7,12 @@ public sealed class LoginCredentials : IEquatable<LoginCredentials>
public string? Identifier { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? CharacterName { get; set; }
public bool Equals(LoginCredentials? other)
{
return this?.Identifier?.Equals(other?.Identifier) is true &&
this?.Username?.Equals(other?.Username) is true &&
this?.Password?.Equals(other?.Password) is true &&
this?.CharacterName?.Equals(other?.CharacterName) is true;
this?.Password?.Equals(other?.Password) is true;
}
public override bool Equals(object? obj)
@@ -24,6 +22,6 @@ public sealed class LoginCredentials : IEquatable<LoginCredentials>
public override int GetHashCode()
{
return HashCode.Combine(this.Identifier, this.Username, this.Password, this.CharacterName);
return HashCode.Combine(this.Identifier, this.Username, this.Password);
}
}
@@ -1,4 +1,5 @@
using Daybreak.Configuration.Options;
using Daybreak.Services.Browser;
using Daybreak.Services.Drawing;
using Daybreak.Services.Metrics;
using Daybreak.Services.Mods;
@@ -34,6 +35,7 @@ public abstract class PluginConfigurationBase
public virtual void RegisterOptions(IOptionsProducer optionsProducer) { }
public virtual void RegisterNotificationHandlers(INotificationHandlerProducer notificationHandlerProducer) { }
public virtual void RegisterMods(IModsManager modsManager) { }
public virtual void RegisterBrowserExtensions(IBrowserExtensionsProducer browserExtensionsProducer) { }
public PluginConfigurationBase()
{
@@ -86,7 +86,6 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
var gwProcess = await this.LaunchGuildwarsProcess(
credentials.Username!.ThrowIfNull(),
credentials.Password!.ThrowIfNull(),
credentials.CharacterName!.ThrowIfNull(),
launchConfigurationWithCredentials.ExecutablePath!.ThrowIfNull());
if (gwProcess is null)
{
@@ -164,7 +163,7 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
Application.Current.Shutdown();
}
private async Task<Process?> LaunchGuildwarsProcess(string email, Models.SecureString password, string character, string executable)
private async Task<Process?> LaunchGuildwarsProcess(string email, Models.SecureString password, string executable)
{
if (File.Exists(executable) is false)
{
@@ -176,13 +175,10 @@ internal sealed class ApplicationLauncher : IApplicationLauncher
"-email",
$"\"{email}\"",
"-password",
$"\"{password}\""
$"\"{password}\"",
"-character",
"\"Daybreak\""
};
if (!string.IsNullOrWhiteSpace(character))
{
args.Add("-character");
args.Add($"\"{character}\"");
}
var mods = this.modsManager.GetMods().Where(m => m.IsEnabled && m.IsInstalled).ToList();
var disabledmods = this.modsManager.GetMods().Where(m => !m.IsEnabled && m.IsInstalled).ToList();
@@ -0,0 +1,48 @@
using Microsoft.Web.WebView2.Core;
using Slim;
using System.Core.Extensions;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Daybreak.Services.Browser;
public sealed class BrowserExtensionsManager : IBrowserExtensionsManager, IBrowserExtensionsProducer
{
private readonly IServiceManager serviceManager;
public BrowserExtensionsManager(
IServiceManager serviceManager)
{
this.serviceManager = serviceManager.ThrowIfNull();
}
public void RegisterExtension<T>()
where T : class, IBrowserExtension
{
this.serviceManager.RegisterScoped<T, T>();
}
public async Task InitializeBrowserEnvironment(CoreWebView2Profile coreWebView2Profile, string browserVersion)
{
var existingExtensions = await coreWebView2Profile.GetBrowserExtensionsAsync();
foreach (var extension in this.serviceManager.GetServicesOfType<IBrowserExtension>()
.Where(e => existingExtensions.None(ee => ee.Id == e.ExtensionId)))
{
await extension.CheckAndUpdate(browserVersion);
var extensionPath = await extension.GetExtensionPath();
if (!Directory.Exists(extensionPath))
{
continue;
}
if (!File.Exists(Path.Combine(extensionPath, "manifest.json")))
{
continue;
}
await coreWebView2Profile.AddBrowserExtensionAsync(extensionPath);
}
}
}
@@ -0,0 +1,189 @@
using Daybreak.Controls;
using Daybreak.Models.Browser;
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Core.Extensions;
using System.Linq;
namespace Daybreak.Services.Browser;
public sealed class BrowserHistoryManager : IBrowserHistoryManager, INotifyPropertyChanged
{
private static readonly TimeSpan DebounceWindow = TimeSpan.FromMilliseconds(100);
private DateTime lastOperation = DateTime.Now;
private string lastUrlCache = string.Empty;
private HashSet<ulong> eventIds = [];
private bool userInitiated;
private bool canGoBack;
private bool canGoForward;
private ChromiumBrowserWrapper? browserWrapper;
public event PropertyChangedEventHandler? PropertyChanged;
public bool CanGoBack
{
get => this.canGoBack;
set
{
this.canGoBack = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.CanGoBack)));
}
}
public bool CanGoForward
{
get => this.canGoForward;
set
{
this.canGoForward = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.CanGoForward)));
}
}
public BrowserHistory BrowserHistory { get; } = new();
public BrowserHistoryManager()
{
}
public void InitializeHistoryManager(ChromiumBrowserWrapper chromiumBrowserWrapper)
{
this.browserWrapper = chromiumBrowserWrapper.ThrowIfNull();
this.browserWrapper.WebBrowser.NavigationCompleted += this.WebView_NavigationCompleted;
}
public void UnInitializeHistoryManager()
{
if (this.browserWrapper is null)
{
return;
}
this.browserWrapper.WebBrowser.NavigationCompleted -= this.WebView_NavigationCompleted;
}
public void GoBack()
{
if (this.browserWrapper is null)
{
return;
}
if (!this.CanGoBack)
{
return;
}
// Skip duplicate commands
if (this.userInitiated)
{
return;
}
this.userInitiated = true;
this.BrowserHistory.CurrentPosition--;
var url = this.BrowserHistory.History[this.BrowserHistory.CurrentPosition];
this.browserWrapper.WebBrowser.CoreWebView2.Navigate(url);
this.CanGoBack = this.BrowserHistory.CurrentPosition > 0;
this.CanGoForward = this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1;
}
public void GoForward()
{
if (this.browserWrapper is null)
{
return;
}
if (!this.CanGoForward)
{
return;
}
// Skip duplicate commands
if (this.userInitiated)
{
return;
}
this.userInitiated = true;
this.BrowserHistory.CurrentPosition++;
var url = this.BrowserHistory.History[this.BrowserHistory.CurrentPosition];
this.browserWrapper.WebBrowser.CoreWebView2.Navigate(url);
this.CanGoBack = this.BrowserHistory.CurrentPosition > 0;
this.CanGoForward = this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1;
}
public void Reload()
{
this.browserWrapper?.WebBrowser.Reload();
}
public void SetBrowserHistory(BrowserHistory browserHistory)
{
this.BrowserHistory.History = browserHistory.History;
this.BrowserHistory.CurrentPosition = browserHistory.CurrentPosition;
this.CanGoBack = this.BrowserHistory.CurrentPosition > 0;
this.CanGoForward = this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1;
if (this.BrowserHistory.History.Count > 0 &&
this.browserWrapper is not null)
{
var url = this.BrowserHistory.History[this.BrowserHistory.CurrentPosition];
this.userInitiated = true;
this.browserWrapper.Address = url;
}
}
public string? GetCurrentAddress()
{
return this.BrowserHistory.History.Skip(this.BrowserHistory.CurrentPosition).FirstOrDefault();
}
private void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (this.eventIds.Contains(e.NavigationId))
{
return;
}
// WebView2 sometimes calls navigation twice for the same url. This condition is supposed to handle the duplicate case
if (this.browserWrapper?.Address == this.lastUrlCache)
{
return;
}
// WebView2 when going forward will sometimes call navigation to the old url and quickly call navigation to new url. This condition is supposed to handle the duplicate operation
if (DateTime.Now - this.lastOperation < DebounceWindow)
{
return;
}
this.eventIds.Add(e.NavigationId);
// Ignore the navigation since it was initiated by the user to either go back or forward
if (this.userInitiated is true)
{
this.userInitiated = false;
this.CanGoBack = this.BrowserHistory.CurrentPosition > 0;
this.CanGoForward = this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1;
this.lastUrlCache = this.browserWrapper?.Address ?? string.Empty;
this.lastOperation = DateTime.Now;
return;
}
if (this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1)
{
this.BrowserHistory.History.RemoveRange(this.BrowserHistory.CurrentPosition + 1, this.BrowserHistory.History.Count - this.BrowserHistory.CurrentPosition - 1);
}
this.BrowserHistory.History.Add(this.browserWrapper?.Address ?? string.Empty);
this.BrowserHistory.CurrentPosition = this.BrowserHistory.History.Count - 1;
this.CanGoBack = this.BrowserHistory.CurrentPosition > 0;
this.CanGoForward = this.BrowserHistory.CurrentPosition < this.BrowserHistory.History.Count - 1;
this.lastUrlCache = this.browserWrapper?.Address ?? string.Empty;
this.lastOperation = DateTime.Now;
}
}
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace Daybreak.Services.Browser;
public interface IBrowserExtension
{
string ExtensionId { get; }
Task CheckAndUpdate(string browserVersion);
Task<string> GetExtensionPath();
}
@@ -0,0 +1,9 @@
using Microsoft.Web.WebView2.Core;
using System.Threading.Tasks;
namespace Daybreak.Services.Browser;
public interface IBrowserExtensionsManager
{
Task InitializeBrowserEnvironment(CoreWebView2Profile coreWebView2Profile, string browserVersion);
}
@@ -0,0 +1,6 @@
namespace Daybreak.Services.Browser;
public interface IBrowserExtensionsProducer
{
void RegisterExtension<T>()
where T : class, IBrowserExtension;
}
@@ -0,0 +1,18 @@
using Daybreak.Controls;
using Daybreak.Models.Browser;
namespace Daybreak.Services.Browser;
public interface IBrowserHistoryManager
{
bool CanGoBack { get; }
bool CanGoForward { get; }
BrowserHistory BrowserHistory { get; }
void SetBrowserHistory(BrowserHistory browserHistory);
void InitializeHistoryManager(ChromiumBrowserWrapper chromiumBrowserWrapper);
void UnInitializeHistoryManager();
void GoBack();
void GoForward();
string? GetCurrentAddress();
void Reload();
}
@@ -70,7 +70,6 @@ internal sealed class CredentialManager : ICredentialManager
{
return new LoginCredentials
{
CharacterName = string.Empty,
Username = string.Empty,
Password = string.Empty,
Identifier = Guid.NewGuid().ToString()
@@ -88,7 +87,6 @@ internal sealed class CredentialManager : ICredentialManager
Identifier = protectedLoginCredentials.Identifier,
Username = Encoding.UTF8.GetString(ProtectedData.Unprotect(usrbytes, Entropy, DataProtectionScope.LocalMachine)),
Password = Encoding.UTF8.GetString(ProtectedData.Unprotect(psdBytes, Entropy, DataProtectionScope.LocalMachine)),
CharacterName = protectedLoginCredentials.CharacterName,
};
}
catch (Exception e)
@@ -109,7 +107,6 @@ internal sealed class CredentialManager : ICredentialManager
Identifier = loginCredentials.Identifier,
ProtectedUsername = Convert.ToBase64String(ProtectedData.Protect(usrBytes, Entropy, DataProtectionScope.LocalMachine)),
ProtectedPassword = Convert.ToBase64String(ProtectedData.Protect(psdBytes, Entropy, DataProtectionScope.LocalMachine)),
CharacterName = loginCredentials.CharacterName,
};
}
catch
@@ -48,7 +48,7 @@ internal sealed class DownloadService : IDownloadService
var fileInfo = new FileInfo(destinationPath);
fileInfo.Directory?.Create();
var fileStream = File.Open(destinationPath, FileMode.Create, FileAccess.Write);
var downloadSize = (double)response.Content!.Headers!.ContentLength!;
var downloadSize = response.Content?.Headers?.ContentLength ?? double.MaxValue;
var buffer = new byte[1024];
var length = 0;
var downloaded = 0d;
@@ -1,32 +1,41 @@
using Daybreak.Attributes;
using Daybreak.Configuration.Options;
using Daybreak.Services.Graph;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.Options;
public sealed class OptionsSynchronizationService : IOptionsSynchronizationService
public sealed class OptionsSynchronizationService : IOptionsSynchronizationService, IApplicationLifetimeService
{
private static readonly TimeSpan StartupDelay = TimeSpan.FromMinutes(1);
private static readonly TimeSpan BackupFrequency = TimeSpan.FromSeconds(15);
private readonly IOptionsProvider optionsProvider;
private readonly IGraphClient graphClient;
private readonly ILiveOptions<LauncherOptions> liveOptions;
private readonly ILogger<OptionsSynchronizationService> logger;
public OptionsSynchronizationService(
IOptionsProvider optionsProvider,
IGraphClient graphClient,
ILiveOptions<LauncherOptions> liveOptions,
ILogger<OptionsSynchronizationService> logger)
{
this.optionsProvider = optionsProvider.ThrowIfNull();
this.graphClient = graphClient.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}
@@ -134,4 +143,32 @@ public sealed class OptionsSynchronizationService : IOptionsSynchronizationServi
return optionsNameAttribute.Name!;
}
public async void OnStartup()
{
await Task.Delay(StartupDelay);
while (true)
{
await Task.Delay(BackupFrequency);
var remoteOptions = await this.GetRemoteOptionsInternal(CancellationToken.None);
var remoteOptionsSerialized = JsonConvert.SerializeObject(remoteOptions);
var currentOptions = JsonConvert.SerializeObject(this.GetCurrentOptionsInternal());
if (remoteOptions is not null &&
currentOptions != remoteOptionsSerialized &&
this.liveOptions.Value.AutoBackupSettings)
{
try
{
await this.BackupOptions(CancellationToken.None);
}
catch
{
}
}
}
}
public void OnClosing()
{
}
}
+3 -1
View File
@@ -1,4 +1,5 @@
using Daybreak.Models.Plugins;
using Daybreak.Services.Browser;
using Daybreak.Services.Drawing;
using Daybreak.Services.Mods;
using Daybreak.Services.Navigation;
@@ -26,7 +27,8 @@ public interface IPluginsService
IStartupActionProducer startupActionProducer,
IDrawingModuleProducer drawingModuleProducer,
INotificationHandlerProducer notificationHandlerProducer,
IModsManager modsManager);
IModsManager modsManager,
IBrowserExtensionsProducer browserExtensionsProducer);
IEnumerable<AvailablePlugin> GetAvailablePlugins();
+53 -39
View File
@@ -1,5 +1,6 @@
using Daybreak.Configuration.Options;
using Daybreak.Models.Plugins;
using Daybreak.Services.Browser;
using Daybreak.Services.Drawing;
using Daybreak.Services.Mods;
using Daybreak.Services.Navigation;
@@ -92,7 +93,8 @@ internal sealed class PluginsService : IPluginsService
IStartupActionProducer startupActionProducer,
IDrawingModuleProducer drawingModuleProducer,
INotificationHandlerProducer notificationHandlerProducer,
IModsManager modsManager)
IModsManager modsManager,
IBrowserExtensionsProducer browserExtensionsProducer)
{
serviceManager.ThrowIfNull();
optionsProducer.ThrowIfNull();
@@ -102,6 +104,7 @@ internal sealed class PluginsService : IPluginsService
drawingModuleProducer.ThrowIfNull();
notificationHandlerProducer.ThrowIfNull();
modsManager.ThrowIfNull();
browserExtensionsProducer.ThrowIfNull();
while (!Monitor.TryEnter(Lock)) { }
@@ -135,48 +138,57 @@ internal sealed class PluginsService : IPluginsService
foreach (var result in results)
{
var pluginScopedLogger = this.logger.CreateScopedLogger(nameof(this.LoadPlugins), result.PluginEntry?.Name ?? string.Empty);
var assembly = ExtractAssembly(result);
LogLoadOperation(result, pluginScopedLogger);
if (assembly is null)
try
{
continue;
}
var assembly = ExtractAssembly(result);
LogLoadOperation(result, pluginScopedLogger);
var entryPoint = assembly.GetTypes().FirstOrDefault(t => t.IsSubclassOf(typeof(PluginConfigurationBase)));
if (entryPoint is null)
if (assembly is null)
{
continue;
}
var entryPoint = assembly.GetTypes().FirstOrDefault(t => t.IsSubclassOf(typeof(PluginConfigurationBase)));
if (entryPoint is null)
{
pluginScopedLogger.LogError($"Assembly loaded but unable to find entry point. The plugin will not start");
continue;
}
var pluginConfig = Activator.CreateInstance(entryPoint)?.As<PluginConfigurationBase>();
if (pluginConfig is null)
{
pluginScopedLogger.LogError($"Assembly loaded but unable to create entry point. The plugin will not start");
continue;
}
RegisterResolvers(pluginConfig, serviceManager);
pluginScopedLogger.LogInformation("Registered resolvers");
RegisterServices(pluginConfig, serviceManager);
pluginScopedLogger.LogInformation("Registered services");
RegisterOptions(pluginConfig, optionsProducer);
pluginScopedLogger.LogInformation("Registered options");
RegisterViews(pluginConfig, viewManager);
pluginScopedLogger.LogInformation("Registered views");
RegisterPostUpdateActions(pluginConfig, postUpdateActionProducer);
pluginScopedLogger.LogInformation("Registered post-update actions");
RegisterStartupActions(pluginConfig, startupActionProducer);
pluginScopedLogger.LogInformation("Registered startup actions");
RegisterDrawingModules(pluginConfig, drawingModuleProducer);
pluginScopedLogger.LogInformation("Registered drawing modules");
RegisterNotificationHandlers(pluginConfig, notificationHandlerProducer);
pluginScopedLogger.LogInformation("Registered notification handlers");
RegisterMods(pluginConfig, modsManager);
pluginScopedLogger.LogInformation("Registered mods");
RegisterBrowserExtensions(pluginConfig, browserExtensionsProducer);
pluginScopedLogger.LogInformation("Registered browser extensions");
this.loadedPlugins.Add(new AvailablePlugin { Name = result.PluginEntry?.Name ?? string.Empty, Path = result.PluginEntry?.Path ?? string.Empty, Enabled = true });
pluginScopedLogger.LogInformation("Loaded plugin");
}
catch(Exception e)
{
pluginScopedLogger.LogError($"Assembly loaded but unable to find entry point. The plugin will not start");
continue;
pluginScopedLogger.LogError(e, $"Encountered exception while loading plugin");
}
var pluginConfig = Activator.CreateInstance(entryPoint)?.As<PluginConfigurationBase>();
if (pluginConfig is null)
{
pluginScopedLogger.LogError($"Assembly loaded but unable to create entry point. The plugin will not start");
continue;
}
RegisterResolvers(pluginConfig, serviceManager);
pluginScopedLogger.LogInformation("Registered resolvers");
RegisterServices(pluginConfig, serviceManager);
pluginScopedLogger.LogInformation("Registered services");
RegisterOptions(pluginConfig, optionsProducer);
pluginScopedLogger.LogInformation("Registered options");
RegisterViews(pluginConfig, viewManager);
pluginScopedLogger.LogInformation("Registered views");
RegisterPostUpdateActions(pluginConfig, postUpdateActionProducer);
pluginScopedLogger.LogInformation("Registered post-update actions");
RegisterStartupActions(pluginConfig, startupActionProducer);
pluginScopedLogger.LogInformation("Registered startup actions");
RegisterDrawingModules(pluginConfig, drawingModuleProducer);
pluginScopedLogger.LogInformation("Registered drawing modules");
RegisterNotificationHandlers(pluginConfig, notificationHandlerProducer);
pluginScopedLogger.LogInformation("Registered notification handlers");
RegisterMods(pluginConfig, modsManager);
pluginScopedLogger.LogInformation("Registered mods");
this.loadedPlugins.Add(new AvailablePlugin { Name = result.PluginEntry?.Name ?? string.Empty, Path = result.PluginEntry?.Path ?? string.Empty, Enabled = true });
pluginScopedLogger.LogInformation("Loaded plugin");
}
Monitor.Exit(Lock);
@@ -309,4 +321,6 @@ internal sealed class PluginsService : IPluginsService
private static void RegisterNotificationHandlers(PluginConfigurationBase pluginConfig, INotificationHandlerProducer notificationHandlerProducer) => pluginConfig.RegisterNotificationHandlers(notificationHandlerProducer);
private static void RegisterMods(PluginConfigurationBase pluginConfig, IModsManager modsManager) => pluginConfig.RegisterMods(modsManager);
private static void RegisterBrowserExtensions(PluginConfigurationBase pluginConfig, IBrowserExtensionsProducer browserExtensionsProducer) => pluginConfig.RegisterBrowserExtensions(browserExtensionsProducer);
}
@@ -0,0 +1,185 @@
using Daybreak.Models.Github;
using Daybreak.Models.Progress;
using System.Collections.Generic;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Core.Extensions;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using System.Extensions;
using System.Linq;
using Daybreak.Services.Downloads;
using Ionic.Zip;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Daybreak.Services.Notifications;
using Daybreak.Services.Browser;
namespace Daybreak.Services.UBlockOrigin;
public sealed class UBlockOriginService : IBrowserExtension
{
private const string TagPlaceholder = "[TAG_PLACEHOLDER]";
private const string ReleaseUrl = "https://github.com/gorhill/uBlock/releases/download/[TAG_PLACEHOLDER]/uBlock0_[TAG_PLACEHOLDER].chromium.zip";
private const string ReleasesUrl = "https://api.github.com/repos/gorhill/uBlock/git/refs/tags";
private const string InstallationPath = "BrowserExtensions";
private const string ZipName = "ublock.chromium.zip";
private const string InstallationFolderName = "uBlock0.chromium";
private static readonly SemaphoreSlim SemaphoreSlim = new(1);
private static volatile bool VersionUpToDate;
private readonly INotificationService notificationService;
private readonly IDownloadService downloadService;
private readonly IHttpClient<UBlockOriginService> httpClient;
private readonly ILogger<UBlockOriginService> logger;
public UBlockOriginService(
INotificationService notificationService,
IDownloadService downloadService,
IHttpClient<UBlockOriginService> httpClient,
ILogger<UBlockOriginService> logger)
{
this.notificationService = notificationService.ThrowIfNull();
this.downloadService = downloadService.ThrowIfNull();
this.httpClient = httpClient.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}
public string ExtensionId { get; } = "uBlock-Origin";
public async Task CheckAndUpdate(string browserVersion)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.CheckAndUpdate), string.Empty);
await SemaphoreSlim.WaitAsync();
try
{
await this.CheckAndUpdateInternal(browserVersion);
}
catch (Exception ex)
{
scopedLogger.LogError(ex, "Encountered exception");
}
SemaphoreSlim.Release();
}
public Task<string> GetExtensionPath()
{
return Task.FromResult(Path.GetFullPath(Path.Combine(InstallationPath, InstallationFolderName)));
}
private async Task CheckAndUpdateInternal(string browserVersion)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.CheckAndUpdateInternal), string.Empty);
if (VersionUpToDate)
{
return;
}
using var cancellationTokenSource = new CancellationTokenSource(5000);
var currentVersionString = await this.GetCurrentVersion(cancellationTokenSource.Token);
var latestVersionString = await this.GetLatestVersion(cancellationTokenSource.Token);
if (latestVersionString is null)
{
scopedLogger.LogError("Failed to retrieve latest uBlock-Origin version");
return;
}
if (currentVersionString is not null &&
Models.Versioning.Version.TryParse(currentVersionString, out var currentVersion) &&
Models.Versioning.Version.TryParse(latestVersionString, out var latestVersion) &&
currentVersion.CompareTo(latestVersion) >= 0)
{
scopedLogger.LogInformation("uBlock-Origin is up to date");
return;
}
this.notificationService.NotifyInformation(
title:"Updating uBlock-Origin",
description: "Updating uBlock-Origin browser extension");
var zipFilePath = await this.DownloadVersion(latestVersionString, CancellationToken.None);
if (zipFilePath is null)
{
scopedLogger.LogError("Failed to retrieve latest uBlock-Origin");
this.notificationService.NotifyInformation(
title: "Failed to update uBlock-Origin",
description: "Failed to update uBlock-Origin");
return;
}
using var zipFile = ZipFile.Read(zipFilePath);
zipFile.ExtractAll(Path.GetFullPath(InstallationPath), ExtractExistingFileAction.OverwriteSilently);
zipFile.Dispose();
File.Delete(zipFilePath);
VersionUpToDate = true;
this.notificationService.NotifyInformation(
title: "Updated uBlock-Origin",
description: "Updated uBlock-Origin browser extension");
}
private async Task<string?> DownloadVersion(string version, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetLatestVersion), version);
scopedLogger.LogInformation($"Retrieving version {version}");
var downloadUrl = ReleaseUrl.Replace(TagPlaceholder, version);
var destinationFolder = Path.GetFullPath(InstallationPath);
var destinationPath = Path.Combine(destinationFolder, ZipName);
var success = await this.downloadService.DownloadFile(downloadUrl, destinationPath, new UpdateStatus(), cancellationToken);
if (!success)
{
throw new InvalidOperationException($"Failed to download uMod version {version}");
}
return destinationPath;
}
private async Task<string?> GetCurrentVersion(CancellationToken cancellationToken)
{
var manifestFilePath = Path.GetFullPath(Path.Combine(InstallationPath, Path.Combine(InstallationFolderName, "manifest.json")));
var fileInfo = new FileInfo(manifestFilePath);
if (!fileInfo.Exists)
{
return default;
}
var manifest = JsonConvert.DeserializeObject<JObject>(await File.ReadAllTextAsync(fileInfo.FullName, cancellationToken));
if (manifest?.TryGetValue("version", out var token) is not true ||
token is not JValue tokenValue ||
tokenValue.Value is not string value)
{
return default;
}
return value;
}
private async Task<string?> GetLatestVersion(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetLatestVersion), string.Empty);
scopedLogger.LogInformation("Retrieving version list");
var getListResponse = await this.httpClient.GetAsync(ReleasesUrl, cancellationToken);
if (!getListResponse.IsSuccessStatusCode)
{
scopedLogger.LogError($"Received non success status code [{getListResponse.StatusCode}]");
return default;
}
var responseString = await getListResponse.Content.ReadAsStringAsync();
var releasesList = responseString.Deserialize<List<GithubRefTag>>();
var latestRelease = releasesList?
.Select(t => t.Ref?.Replace("refs/tags/", ""))
.OfType<string>()
.Where(v => !v.Contains("b") && !v.Contains("rc"))
.LastOrDefault();
if (latestRelease is not string tag)
{
scopedLogger.LogError("Could not parse version list. No latest version found");
return default;
}
return latestRelease;
}
}
+25
View File
@@ -63,4 +63,29 @@ public static class Scripts
window.chrome.webview.postMessage(jsonObject);
return jsonObject;
}";
public const string CaptureNavigationButtons = @"
(function() {
if (!document._navigationButtonListenerAdded) {
document.addEventListener('mouseup', function(event) {
if (event.button === 3) {
let jsonObject =
{
Key: 'XButton1Pressed'
};
window.chrome.webview.postMessage(jsonObject);
event.preventDefault();
} else if (event.button === 4) {
let jsonObject =
{
Key: 'XButton2Pressed'
};
window.chrome.webview.postMessage(jsonObject);
event.preventDefault();
}
});
document._navigationButtonListenerAdded = true;
}
})();
";
}
+5 -5
View File
@@ -61,15 +61,15 @@ public partial class FocusView : UserControl
[GenerateDependencyProperty]
private MainPlayerResourceContext mainPlayerResourceContext = new();
[GenerateDependencyProperty]
private string browserAddress = string.Empty;
[GenerateDependencyProperty]
private bool loadingPathingData;
[GenerateDependencyProperty]
private bool faultyPathingData;
[GenerateDependencyProperty]
private string browserAddress = string.Empty;
[GenerateDependencyProperty]
private bool inventoryVisible;
@@ -106,7 +106,7 @@ public partial class FocusView : UserControl
if (e.Property == BrowserAddressProperty &&
this.Browser.BrowserEnabled)
{
this.liveUpdateableOptions.Value.BrowserUrl = this.BrowserAddress;
this.liveUpdateableOptions.Value.BrowserHistory = this.Browser.BrowserHistoryManager.BrowserHistory;
this.liveUpdateableOptions.UpdateOption();
}
else if (e.Property == DataContextProperty &&
@@ -502,7 +502,7 @@ public partial class FocusView : UserControl
return;
}
this.BrowserAddress = this.liveUpdateableOptions.Value.BrowserUrl;
this.Browser.BrowserHistoryManager.SetBrowserHistory(this.liveUpdateableOptions.Value.BrowserHistory);
this.InventoryVisible = this.liveUpdateableOptions.Value.InventoryComponentVisible;
this.MinimapVisible = this.liveUpdateableOptions.Value.MinimapComponentVisible;
this.cancellationTokenSource?.Dispose();
-1
View File
@@ -26,7 +26,6 @@
<DataTemplate>
<controls:AccountTemplate Username="{Binding Username, Mode=TwoWay}"
Password="{Binding Password, Mode=TwoWay}"
CharacterName="{Binding CharacterName, Mode=TwoWay}"
RemoveClicked="AccountTemplate_RemoveClicked"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
@@ -62,9 +62,6 @@
<TextBlock Text="{Binding Credentials.Username}"
FontSize="16"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding Credentials.CharacterName}"
FontSize="12"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
<TextBlock Text="{Binding ExecutablePath}"
FontSize="12"
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"/>
+1 -1
Submodule GWCA updated: 08047d79a5...37ff5a33a4