mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-25 08:22:07 +00:00
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user