Bugfixes and improvements (#176)

Closes #175
Closes #174
Closes #173
This commit is contained in:
2023-03-09 19:53:28 +00:00
committed by GitHub
parent 5ea8df7cb9
commit 6ea77f7e05
5 changed files with 66 additions and 82 deletions
@@ -11,6 +11,7 @@ using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -27,9 +28,13 @@ public partial class ChromiumBrowserWrapper : UserControl
{
public static readonly DependencyProperty AddressProperty =
DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
private const string BrowserSearchPlaceholder = "[PLACEHOLDER]";
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 CoreWebView2Environment? coreWebView2Environment;
public event EventHandler<string>? FavoriteUriChanged;
@@ -196,14 +201,19 @@ public partial class ChromiumBrowserWrapper : UserControl
{
if (e.Key == System.Windows.Input.Key.Enter)
{
var newAddress = sender.As<TextBox>().Text;
newAddress = SanitizeAddress(newAddress);
if (newAddress.IsNullOrWhiteSpace())
var input = sender.As<TextBox>().Text;
var maybeAddress = SanitizeAddress(input);
// If input is address, navigate to address. Otherwise search for the text input in Google
if (Uri.TryCreate(maybeAddress, UriKind.Absolute, out _))
{
return;
this.Address = maybeAddress;
}
else
{
var address = BrowserSearchLink.Replace(BrowserSearchPlaceholder, maybeAddress);
this.Address = address;
}
this.Address = newAddress;
this.WebBrowser.CoreWebView2.Navigate(this.Address);
e.Handled = true;
}
@@ -334,6 +344,11 @@ public partial class ChromiumBrowserWrapper : UserControl
return default!;
}
if (WebAddressRegex.IsMatch(address) is false)
{
return address;
}
if (address.StartsWith("www") is false &&
address.StartsWith("http") is false)
{
+1 -1
View File
@@ -13,7 +13,7 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.8.16</Version>
<Version>0.9.8.17</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
</PropertyGroup>
@@ -4,6 +4,7 @@ using Daybreak.Models.Builds;
using Daybreak.Models.Guildwars;
using Daybreak.Models.Interop;
using Daybreak.Models.Metrics;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Metrics;
using Microsoft.Extensions.Logging;
using System;
@@ -16,18 +17,18 @@ using System.Extensions;
using System.Linq;
using System.Logging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Daybreak.Services.Scanner;
public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
{
private const int RetryInitializationCount = 15;
private const string LatencyMeterName = "Memory Reader Latency";
private const string LatencyMeterUnitsName = "Milliseconds";
private const string LatencyMeterDescription = "Amount of milliseconds elapsed while reading memory. P95 aggregation";
private readonly IApplicationLauncher applicationLauncher;
private readonly IMemoryScanner memoryScanner;
private readonly Histogram<double> latencyMeter;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
@@ -36,32 +37,26 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
private IntPtr playerIdPointer;
private IntPtr entityArrayPointer;
private IntPtr titleDataPointer;
private volatile CancellationTokenSource? cancellationTokenSource;
public bool Faulty { get; private set; }
public bool Running => this.cancellationTokenSource is not null && this.cancellationTokenSource.IsCancellationRequested is false;
public GameData? GameData { get; private set; }
public Process? TargetProcess => this.memoryScanner.Process;
public GuildwarsMemoryReader(
IApplicationLauncher applicationLauncher,
IMemoryScanner memoryScanner,
IMetricsService metricsService,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<GuildwarsMemoryReader> logger)
{
this.applicationLauncher = applicationLauncher.ThrowIfNull();
this.memoryScanner = memoryScanner.ThrowIfNull();
this.latencyMeter = metricsService.ThrowIfNull().CreateHistogram<double>(LatencyMeterName, LatencyMeterUnitsName, LatencyMeterDescription, AggregationTypes.P95);
this.liveOptions = liveOptions.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}
public async void Initialize(Process process)
public async Task EnsureInitialized()
{
var scoppedLogger = this.logger.CreateScopedLogger(nameof(this.Initialize), default);
if (process is null)
var scoppedLogger = this.logger.CreateScopedLogger(nameof(this.EnsureInitialized), default);
var currentGuildwarsProcess = this.applicationLauncher.RunningGuildwarsProcess;
if (currentGuildwarsProcess is null)
{
scoppedLogger.LogWarning($"Process is null. {nameof(GuildwarsMemoryReader)} will not start");
return;
@@ -69,7 +64,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
try
{
await this.InitializeSafe(process, scoppedLogger);
await this.InitializeSafe(currentGuildwarsProcess, scoppedLogger);
}
catch(Exception e)
{
@@ -81,7 +76,17 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
{
var scoppedLogger = this.logger.CreateScopedLogger(nameof(this.Stop), default);
scoppedLogger.LogInformation($"Stopping {nameof(GuildwarsMemoryReader)}");
this.cancellationTokenSource?.Cancel();
this.memoryScanner?.EndScanner();
}
public Task<GameData?> ReadGameData()
{
if (this.memoryScanner.Scanning is false)
{
return Task.FromResult<GameData?>(default);
}
return Task.Run(this.SafeReadGameMemory);
}
private async Task InitializeSafe(Process process, ScopedLogger<GuildwarsMemoryReader> scopedLogger)
@@ -104,9 +109,6 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
{
await this.ResilientBeginScanner(scopedLogger, process!);
}
this.cancellationTokenSource?.Cancel();
this.PeriodicallyReadGuildwarsMemory();
}
private async Task ResilientBeginScanner(ScopedLogger<GuildwarsMemoryReader> scopedLogger, Process process)
@@ -127,50 +129,26 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
}
}
private void PeriodicallyReadGuildwarsMemory()
{
var cancellationTokenSource = new CancellationTokenSource();
this.cancellationTokenSource = cancellationTokenSource;
_ = Task.Run(() => this.RecursivelyReadGameMemory(this.cancellationTokenSource.Token), this.cancellationTokenSource.Token);
}
private async Task RecursivelyReadGameMemory(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
this.SafeReadGameMemory(cancellationToken);
await Task.Delay((int)this.liveOptions.Value.ExperimentalFeatures.MemoryReaderFrequency);
_ = Task.Run(() => this.RecursivelyReadGameMemory(cancellationToken), cancellationToken);
}
private void SafeReadGameMemory(CancellationToken cancellationToken)
private GameData? SafeReadGameMemory()
{
try
{
var stopWatch = Stopwatch.StartNew();
this.ReadGameMemory(cancellationToken);
var gameData = this.ReadGameMemory();
stopWatch.Stop();
this.latencyMeter.Record(stopWatch.Elapsed.TotalMilliseconds);
this.Faulty = false;
return gameData;
}
catch(Exception e)
{
this.GameData = null;
this.Faulty = true;
this.logger.LogError(e, "Exception encountered when reading game memory");
return default;
}
}
private void ReadGameMemory(CancellationToken cancellationToken)
private GameData? ReadGameMemory()
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
/*
* The following offsets were reverse-engineered using pointer scanning. All of the following ones seem to currently work.
* If any breaks, try the other ones from the list below.
@@ -186,8 +164,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
globalContext.UserContext.ToInt32() == 0x0 ||
globalContext.InstanceContext.ToInt32() == 0x0)
{
this.GameData = new GameData { Valid = false };
return;
return new GameData { Valid = false };
}
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
@@ -217,7 +194,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
//var entityArray = this.memoryScanner.ReadPtrChain<GuildwarsArray>(this.GetEntityArrayPointer(), 0x0, 0x0);
//var entities = this.memoryScanner.ReadArray<EntityContext>(entityArray);
this.GameData = this.AggregateGameData(
return this.AggregateGameData(
gameContext,
instanceContext,
mapEntities,
@@ -555,12 +532,4 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
return str;
}
public void Dispose()
{
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource?.Dispose();
this.cancellationTokenSource = null;
this.Faulty = false;
}
}
@@ -1,14 +1,11 @@
using Daybreak.Models;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Daybreak.Services.Scanner;
public interface IGuildwarsMemoryReader
{
bool Faulty { get; }
bool Running { get; }
GameData? GameData { get; }
Process? TargetProcess { get; }
void Initialize(Process process);
Task EnsureInitialized();
Task<GameData?> ReadGameData();
void Stop();
}
+12 -9
View File
@@ -136,7 +136,7 @@ public partial class FocusView : UserControl
base.OnPropertyChanged(e);
}
private void UpdateGameData()
private async void UpdateGameData()
{
if (this.applicationLauncher.IsGuildwarsRunning is false)
{
@@ -144,15 +144,17 @@ public partial class FocusView : UserControl
this.viewManager.ShowView<LauncherView>();
}
if (this.guildwarsMemoryReader.Running is false ||
this.guildwarsMemoryReader.Faulty)
await this.guildwarsMemoryReader.EnsureInitialized();
var maybeGameData = await this.guildwarsMemoryReader.ReadGameData();
if (maybeGameData is not GameData gameData)
{
this.guildwarsMemoryReader.Initialize(this.applicationLauncher.RunningGuildwarsProcess!);
return;
}
this.Dispatcher.Invoke(() =>
{
this.GameData = this.guildwarsMemoryReader.GameData;
this.GameData = gameData;
if (this.GameData?.MainPlayer is null ||
this.GameData?.User is null ||
this.GameData?.Session is null)
@@ -169,10 +171,11 @@ public partial class FocusView : UserControl
this.MainPlayerDataValid = this.GameData.Valid && this.GameData.MainPlayer.MaxHealth > 0U && this.GameData.MainPlayer.MaxEnergy > 0U;
if (this.GameData.MainPlayer.TitleInformation is TitleInformation titleInformation && titleInformation.IsValid)
{
if (titleInformation.Title is not null)
if (titleInformation.Title is not null &&
titleInformation.Title.Tiers!.Count > titleInformation.TierNumber - 1)
{
var rankIndex = (int)titleInformation.TierNumber! - 1;
this.TitleRankName = titleInformation.Title.Tiers![rankIndex];
this.TitleRankName = $"{titleInformation.Title.Tiers![rankIndex]} ({titleInformation.TierNumber}/{titleInformation.MaxTierNumber})";
}
else
{
@@ -371,11 +374,11 @@ public partial class FocusView : UserControl
{
if (this.GameData.MainPlayer?.TitleInformation?.IsPercentage is true)
{
this.TitleText = $"{(double?)this.GameData.MainPlayer?.TitleInformation?.CurrentPoints / 10d}%";
this.TitleText = $"{(double?)this.GameData.MainPlayer?.TitleInformation?.CurrentPoints / 10d}% Rank Progress";
}
else
{
this.TitleText = $"{this.GameData.MainPlayer?.TitleInformation?.CurrentPoints}/{this.GameData.MainPlayer?.TitleInformation?.PointsForNextRank}";
this.TitleText = $"{this.GameData.MainPlayer?.TitleInformation?.CurrentPoints}/{this.GameData.MainPlayer?.TitleInformation?.PointsForNextRank} Rank Progress";
}
}