mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
Blazor UI Migration (#1085)
* Migrate to Fluent UI * Setup window events for Blazor WebView * Setup basic FluentUI styling * FluentUI links * Setup views and viewmodel infra * Remove old navigation logic * Prepare an example of iframe embed * Style window + navigation menu * Setup page based navigation * Add some UI error handling * Introduce UI resiliency to exceptions * Setup settings menu dropdown * Basic setup for options views * Setup reusable OptionBase * Setup options views * Basic styling * Setup scoped css files. Split App into components * Hook option updates to OptionProvider * Fix option dropdowns * Setup multiple themes * Make fonts scalable * Move ViewModels under Views * Launch view + styling * Setup FocusView base * Finish BuildListView implementation * Base for build views * Setup build routing * Implement single build editor * Implement skill description on hover * Scrape skill information from the wiki and cache it locally * Scrape and fix Guild Wars wiki info * Improve mouse hover performance by querying position only when needed * Skill snippet snaps to edges * Setup accounts and executables view * Launch configurations view * Fix build skill icons Attach skill types to skills Scrape upkeep from wiki * Fix skilltype overflow * Adjust navigation menu * Setup privilege elevation flow * Setup MSAL * Options synchronization view * Telemetry view * Version management and update view * Setup plugins view * Setup notifications panel * Base for logs view * Finish LogView * Setup metrics view * Setup UI logging interop * GuildWarsPartySearch view implementation * Setup gitlab mirror job * Eventviewer base * Remove gitlab mirror * Remove mirror pipeline * Fix metricsview crashes * Extract progress component * Setup trade chat views * Setup Download view * Extract progress styling * Setup guild wars copy view * Setup trade alerts * Fix team template view skill selection * Setup basic onboarding view * Overhaul asynchronous progress operations and remove redundant versioning code * Setup modsview base * Setup modsview with mod states * Setup mod installation process * Cleanup more ui migration Remove price history #1076 * Screen selector manage view * Upgrade to .net 10 Closes #1078 * gMod management view * Trader message notification * ReShade management * Update for toolbox * Setup focusview fetching * Setup CharacterComponent * Setup vanquishing component * Setup TitleInformationComponent * Closes #1082 Fix API project for Guild Wars Reforged * Small fixes + attempts to get new exe version * FileId fixes * Current Map and Player Resources widgets * Quest log widget * BuildComponent implementation * Closes #1075 Embed wwwroot into the executable * Cleanup * Fixes to slnx * Fix test project
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<TargetFramework>$(TargetFrameworkVersionWindows)</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
@@ -53,7 +53,6 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" />
|
||||
<PackageReference Include="System.Private.Uri" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard.Generators" PrivateAssets="all" />
|
||||
<PackageReference Include="ZLinq" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -155,52 +155,52 @@ public readonly unsafe struct AgentLivingContext
|
||||
[FieldOffset(0x00F6)]
|
||||
public readonly AgentModelType AgentModelType;
|
||||
|
||||
[FieldOffset(0x0104)]
|
||||
[FieldOffset(0x0108)]
|
||||
public readonly TagInfo* Tags;
|
||||
|
||||
[FieldOffset(0x010A)]
|
||||
[FieldOffset(0x010E)]
|
||||
public readonly byte Primary;
|
||||
|
||||
[FieldOffset(0x010B)]
|
||||
[FieldOffset(0x010F)]
|
||||
public readonly byte Secondary;
|
||||
|
||||
[FieldOffset(0x010C)]
|
||||
[FieldOffset(0x0110)]
|
||||
public readonly byte Level;
|
||||
|
||||
[FieldOffset(0x010D)]
|
||||
[FieldOffset(0x0111)]
|
||||
public readonly byte TeamId;
|
||||
|
||||
[FieldOffset(0x0114)]
|
||||
[FieldOffset(0x0118)]
|
||||
public readonly float EnergyRegen;
|
||||
|
||||
[FieldOffset(0x011C)]
|
||||
[FieldOffset(0x0120)]
|
||||
public readonly float Energy;
|
||||
|
||||
[FieldOffset(0x0120)]
|
||||
[FieldOffset(0x0124)]
|
||||
public readonly uint MaxEnergy;
|
||||
|
||||
[FieldOffset(0x0128)]
|
||||
[FieldOffset(0x012C)]
|
||||
public readonly float HealthRegen;
|
||||
|
||||
[FieldOffset(0x0130)]
|
||||
[FieldOffset(0x0134)]
|
||||
public readonly float Health;
|
||||
|
||||
[FieldOffset(0x0134)]
|
||||
[FieldOffset(0x0138)]
|
||||
public readonly uint MaxHealth;
|
||||
|
||||
[FieldOffset(0x0138)]
|
||||
[FieldOffset(0x013C)]
|
||||
public readonly uint Effects;
|
||||
|
||||
[FieldOffset(0x0154)]
|
||||
[FieldOffset(0x0158)]
|
||||
public readonly LivingAgentModelState ModelState;
|
||||
|
||||
[FieldOffset(0x0158)]
|
||||
[FieldOffset(0x015C)]
|
||||
public readonly LivingAgentState State;
|
||||
|
||||
[FieldOffset(0x0180)]
|
||||
[FieldOffset(0x0184)]
|
||||
public readonly uint LoginNumber;
|
||||
|
||||
[FieldOffset(0x01B1)]
|
||||
[FieldOffset(0x01B5)]
|
||||
public readonly LivingAgentAllegiance Allegiance;
|
||||
|
||||
public readonly bool IsAlive => (!this.State.HasFlag(LivingAgentState.Dead)) && this.Health > 0f;
|
||||
|
||||
@@ -19,25 +19,25 @@ public readonly struct Frame
|
||||
[FieldOffset(0x0024)]
|
||||
public readonly uint TemplateType;
|
||||
|
||||
[FieldOffset(0x00A0)]
|
||||
[FieldOffset(0x00A8)]
|
||||
public readonly GuildWarsArray<FrameInteractionCallback> FrameCallbacks;
|
||||
|
||||
[FieldOffset(0x00b0)]
|
||||
[FieldOffset(0x00b8)]
|
||||
public readonly uint ChildOffsetId;
|
||||
|
||||
[FieldOffset(0x00b4)]
|
||||
[FieldOffset(0x00bc)]
|
||||
public readonly uint FrameId;
|
||||
|
||||
[FieldOffset(0x0120)]
|
||||
[FieldOffset(0x0128)]
|
||||
public readonly FrameRelation Relation;
|
||||
|
||||
[FieldOffset(0x0184)]
|
||||
public readonly uint Field91;
|
||||
[FieldOffset(0x018C)]
|
||||
public readonly uint FrameState;
|
||||
|
||||
public bool IsCreated => (this.Field91 & 0x4) != 0;
|
||||
public bool IsHidden => (this.Field91 & 0x200) != 0;
|
||||
public bool IsCreated => (this.FrameState & 0x4) != 0;
|
||||
public bool IsHidden => (this.FrameState & 0x200) != 0;
|
||||
public bool IsVisible => !this.IsHidden;
|
||||
public bool IsDIsabled => (this.Field91 & 0x10) != 0;
|
||||
public bool IsDIsabled => (this.FrameState & 0x10) != 0;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x1C)]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.API.Interop.GuildWars;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x34)]
|
||||
public unsafe struct PartySearchWindowContext
|
||||
{
|
||||
[FieldOffset(0x0000)]
|
||||
public uint* Vtable;
|
||||
[FieldOffset(0x0004)]
|
||||
public uint FrameId;
|
||||
[FieldOffset(0x0020)]
|
||||
public uint SelectedPartySearchId;
|
||||
[FieldOffset(0x0024)]
|
||||
public uint SelectedHeroId;
|
||||
[FieldOffset(0x0028)]
|
||||
public uint SelectedHenchmanId;
|
||||
[FieldOffset(0x002C)]
|
||||
public uint CurrentTab;
|
||||
}
|
||||
@@ -6,13 +6,13 @@ public enum UIMessage : uint
|
||||
Resize = 0x8,
|
||||
InitFrame = 0x9,
|
||||
DestroyFrame = 0xb,
|
||||
KeyDown = 0x1e, // wparam = UIPacket::kKeyAction*
|
||||
KeyUp = 0x20, // wparam = UIPacket::kKeyAction*
|
||||
KeyDown = 0x20, // wparam = UIPacket::kKeyAction*
|
||||
KeyUp = 0x22, // wparam = UIPacket::kKeyAction*
|
||||
MouseClick = 0x22, // wparam = UIPacket::kMouseClick*
|
||||
MouseClick2 = 0x2e, // wparam = UIPacket::kMouseAction*
|
||||
MouseClick2 = 0x31, // wparam = UIPacket::kMouseAction*
|
||||
MouseAction = 0x2f, // wparam = UIPacket::kMouseAction*
|
||||
SetLayout = 0x33,
|
||||
FrameMessage_0x47 = 0x47, // Multiple uses depending on frame
|
||||
FrameMessage_0x47 = 0x55, // Multiple uses depending on frame
|
||||
UpdateAgentEffects = 0x10000000 | 0x9,
|
||||
RerenderAgentModel = 0x10000000 | 0x7, // wparam = uint32_t agent_id
|
||||
AgentSpeechBubble = 0x10000000 | 0x17,
|
||||
@@ -94,7 +94,7 @@ public enum UIMessage : uint
|
||||
PreferenceFlagChanged = 0x10000000 | 0x13F, // wparam = UiPacket::kPreferenceFlagChanged
|
||||
PreferenceValueChanged = 0x10000000 | 0x140, // wparam = UiPacket::kPreferenceValueChanged
|
||||
UIPositionChanged = 0x10000000 | 0x141, // wparam = UIPacket::kUIPositionChanged
|
||||
QuestAdded = 0x10000000 | 0x149, // wparam = { quest_id, ... }
|
||||
QuestAdded = 0x10000000 | 0x14C, // wparam = { quest_id, ... }
|
||||
QuestDetailsChanged = 0x10000000 | 0x14A, // wparam = { quest_id, ... }
|
||||
ClientActiveQuestChanged = 0x10000000 | 0x14C, // wparam = { quest_id, ... }. Triggered when the game requests the current quest to change
|
||||
ServerActiveQuestChanged = 0x10000000 | 0x14E, // wparam = UIPacket::kServerActiveQuestChanged*. Triggered when the server requests the current quest to change
|
||||
|
||||
@@ -11,6 +11,16 @@ public static class UIPackets
|
||||
Right = 0x2
|
||||
}
|
||||
|
||||
public enum ActionState
|
||||
{
|
||||
MouseDown = 0x6,
|
||||
MouseUp = 0x7,
|
||||
MouseClick = 0x8,
|
||||
MouseDoubleClick = 0x9,
|
||||
DragRelease = 0xb,
|
||||
KeyDown = 0xe
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct LogChatMessage(string message, Channel channel)
|
||||
{
|
||||
@@ -43,4 +53,14 @@ public static class UIPackets
|
||||
public readonly uint IsDoubleClick = isDoubleClick;
|
||||
public readonly uint UnknownTypeScreenPos = unknownTypeScreenPos;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct MouseAction(uint frameId, uint childOffsetId, ActionState currentState, nuint wparam = 0, nuint lparam = 0)
|
||||
{
|
||||
public readonly uint FrameId = frameId;
|
||||
public readonly uint ChildOffsetId = childOffsetId;
|
||||
public readonly ActionState CurrentState = currentState;
|
||||
public readonly nuint WParam = wparam;
|
||||
public readonly nuint LParam = lparam;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Daybreak.API.Services.Interop;
|
||||
using Daybreak.API.Interop.GuildWars;
|
||||
using Daybreak.API.Services.Interop;
|
||||
using Daybreak.Shared.Models.Api;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions.Core;
|
||||
@@ -68,8 +69,18 @@ public sealed class CharacterSelectService(
|
||||
charContext.IsPvp));
|
||||
}
|
||||
|
||||
var currentCharacter = availableChars.FirstOrDefault(c => c.Uuid == currentUuid);
|
||||
return new CharacterSelectInformation(currentCharacter, availableChars);
|
||||
// TODO: Reforged sometimes returns Zero UUID even when in-game, need to investigate why
|
||||
if (currentUuid != Uuid.Zero.ToString())
|
||||
{
|
||||
var currentCharacter = availableChars.FirstOrDefault(c => c.Uuid == currentUuid);
|
||||
return new CharacterSelectInformation(currentCharacter, availableChars);
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentCharacter = availableChars.FirstOrDefault();
|
||||
return new CharacterSelectInformation(currentCharacter, availableChars);
|
||||
}
|
||||
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
@@ -272,13 +283,14 @@ public sealed class CharacterSelectService(
|
||||
return false;
|
||||
}
|
||||
|
||||
var uiState = 10U;
|
||||
this.uiContextService.SendMessage(Models.UIMessage.CheckUIState, 0, (uint)&uiState);
|
||||
var loginScreen = uiState == 2;
|
||||
if (!loginScreen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//TODO: Re-enable login screen check once we have a reliable way to detect it
|
||||
//var uiState = 10U;
|
||||
//this.uiContextService.SendMessage(Models.UIMessage.CheckUIState, 0, (uint)&uiState);
|
||||
//var loginScreen = uiState == 2;
|
||||
//if (!loginScreen)
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
|
||||
for (var i = 0U; i < preGameContext.Pointer->LoginCharacters.Size; i++)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
namespace Daybreak.API.Services;
|
||||
|
||||
public sealed class HashingService
|
||||
{
|
||||
private const uint DefaultHash = 0x325D1EAE;
|
||||
private const uint WStringHashSeed1 = 0xE2C15C9D;
|
||||
private const uint WStringHashSeed2 = 0x2170A28A;
|
||||
|
||||
private static readonly uint[] HashTable = [
|
||||
0x2EB3C21D, 0xA6F1D482, 0xBE24628F, 0x35699E30, 0xC87EE224, 0x04BA0097, 0x791F0EAF, 0xA162D466,
|
||||
0x685B376A, 0xF82133BA, 0x4682D46B, 0xCB25C0C5, 0xAC3F9D22, 0xABF17202, 0xEF987728, 0x3E400B83,
|
||||
0x63003108, 0xD83F1C93, 0x5058F507, 0x032E9991, 0x3518BC24, 0x1F90D44B, 0x92508DCE, 0x42DAA15C,
|
||||
0x5B4105A3, 0x4D5026AD, 0x07F838B0, 0xE8C4D2A2, 0xF9EF4F54, 0x1FC1A85F, 0xB9041A40, 0x446F6969,
|
||||
0xACF1EE39, 0x1828CC65, 0xC2F6C59D, 0x2456C3C0, 0x64F70142, 0x9C8ADB42, 0xC76781BF, 0x6AEA07E3,
|
||||
0xE98B14BF, 0x7DBCB35E, 0x6CE78C80, 0x1E3F93DE, 0x0954FEB7, 0xB3103AEF, 0x6DAAC1EE, 0x6535E301,
|
||||
0x4A221E68, 0x819CDD81, 0x5378CE3B, 0xE7348236, 0x2C3A7BCC, 0x31C50234, 0x745319DA, 0xF0897A0B,
|
||||
0x5C182968, 0x48E018F4, 0x593E3BB6, 0xAB588C8D, 0xF1101F1F, 0x1D36E8BC, 0xABC7C7C9, 0xF58AA6C8,
|
||||
0xF75B4B98, 0xC447B576, 0x6E5290F2, 0xD344EDF8, 0x5E1BCAF1, 0xEE892405, 0x798F7609, 0x8905CC29,
|
||||
0x8977DEDC, 0x007850BC, 0x0AA3114A, 0x2F61C8F2, 0x376DFAA4, 0x0BABFC12, 0x98E64E82, 0xE6743711,
|
||||
0xE26B26B6, 0xCBB10C72, 0x87446F8F, 0x96E26C0D, 0xCF140831, 0x81F69B0B, 0x5C9DF30B, 0xB276F929,
|
||||
0x2712C5B6, 0x80EA90D6, 0x8EB30322, 0xCF7A705A, 0x57A17E8C, 0x556A2425, 0x15104549, 0x5F3D549F,
|
||||
0x5D98FDEE, 0x9D8FA73E, 0x9206C9EF, 0xEDA0541D, 0xA3164F2D, 0x04C83543, 0x9BB27230, 0xEE97F0BA,
|
||||
0xB6AEB38C, 0x41FF4DA7, 0x4023EE54, 0xECBB78BC, 0xC2C54733, 0x8850B63F, 0x93BF56EF, 0xC80177D7,
|
||||
0xE68B0C13, 0x3C1E9D4C, 0x5CB3EACC, 0xF16F5795, 0xAC2B8A5C, 0x952E466E, 0x3095B0D5, 0xEC9D3A43,
|
||||
0x23D8FCF7, 0x10C7A93F, 0x1B479FB5, 0x3664CF8F, 0xA514A56B, 0xE16C4886, 0x63828188, 0xA47F14C1,
|
||||
0x391C237C, 0xFB15911F, 0x29825A01, 0xFC66DA40, 0xB2719F4E, 0x7B0D1611, 0x8F1EE7DE, 0xA6A1E77D,
|
||||
0xDFC25D30, 0x76377036, 0x8145DB82, 0x2300A4E9, 0xA2417F88, 0x1B8C6AFC, 0x68EBDC8E, 0x48FC0C41,
|
||||
0x040C3DCD, 0x17F02C44, 0xCC40B026, 0x5089EE1E, 0x2A594C28, 0x0CC198EC, 0x3F676F3D, 0xCD0AA239,
|
||||
0x274A31A4, 0xC6B4D4FA, 0x9480A62F, 0xAF8862EF, 0x627CA44D, 0x572E89D3, 0x4C05136C, 0x60F36E8C,
|
||||
0x7DE31FEF, 0x1B97FED3, 0xED99E2BF, 0xA034D418, 0xBBDB3E04, 0x1F198B71, 0xED45E889, 0xF7F1B0DC,
|
||||
0x1592B9A5, 0x96D60F96, 0x03D616EE, 0xA5EF679D, 0x831D791D, 0x579D5FD1, 0x4B44DC48, 0x38F7A2F0,
|
||||
0x2BF34A28, 0xAF14318C, 0xBD66D20A, 0x9AC72D2A, 0xC41ADB95, 0x860B2205, 0xE0E580E7, 0x3A06669C,
|
||||
0x2D1A40A2, 0x577EDF60, 0x1A37693A, 0x3E576C35, 0xE3857565, 0xE46E75EE, 0x94E3625D, 0x7D1B9280,
|
||||
0x2A0CCCE1, 0xBAB36163, 0x66E4E087, 0x940129BB, 0x59C197CD, 0xB0634139, 0xBB2964E1, 0x17207138,
|
||||
0xD8BD3728, 0xB324876C, 0x7C72FD04, 0xBB5D9D82, 0xD9B40236, 0xC0045BF6, 0xF85100ED, 0x36493582,
|
||||
0x8D0DF92A, 0xC3328368, 0xC202E412, 0x7ADE0064, 0xD01529C6, 0x4445B2AD, 0x0F6FC63B, 0xEF976FC5,
|
||||
0x709EC6BE, 0x69D31EA1, 0x794BFEB4, 0xB522DB95, 0x700DE6DF, 0x49098B71, 0x38B7FA82, 0x11EDDC04,
|
||||
0x5BB21D9F, 0xB14BC6D4, 0x3009D754, 0x451F5F3D, 0xFAA58575, 0xFB190B0D, 0x999DF79F, 0x6798A7AA,
|
||||
0x0BC434FF, 0x429E6C15, 0xA1EA2298, 0x55A57B81, 0xD7D74B8D, 0x8FECB38B, 0x658AD588, 0xF0190227,
|
||||
0xBE5D2B36, 0x62C59613, 0xC9FEDD64, 0xADD07A1E, 0x757A7679, 0xD78B6DCC, 0x56F88B2C, 0x4CC020BB,
|
||||
0xA88B51FE, 0xF47F7691, 0x48587EDD, 0x3FCEC5DB, 0xA7E127AC, 0x561F6161, 0x37AB9607, 0xFE23282B,
|
||||
];
|
||||
|
||||
private static readonly uint[] StringHashTable = [
|
||||
0x92B9A528, 0x25D4FC88, 0xEDCBEFB8, 0x51063A80,
|
||||
0x91341C61, 0x0261229D, 0x726F48ED, 0xCE1C088C,
|
||||
0x76253EB5, 0x31E3A0DE, 0xA2AAD215, 0xCA7D6D27,
|
||||
0xA5F98970, 0x0541C365, 0x3C14FF04, 0x5056AF4F
|
||||
];
|
||||
|
||||
private static uint HashHelper(uint result, byte b)
|
||||
{
|
||||
return result + (HashTable[result >> 24] ^ (result >> 6) ^ HashTable[b]);
|
||||
}
|
||||
|
||||
public uint Hash(byte[] data)
|
||||
{
|
||||
var hash = DefaultHash;
|
||||
foreach(var b in data)
|
||||
{
|
||||
hash = HashHelper(hash, b);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public uint Hash(byte data)
|
||||
{
|
||||
return HashHelper(DefaultHash, data);
|
||||
}
|
||||
|
||||
public uint Hash(short data)
|
||||
{
|
||||
uint hash = DefaultHash;
|
||||
hash = HashHelper(hash, (byte)(data >> 0));
|
||||
hash = HashHelper(hash, (byte)(data >> 8));
|
||||
return hash;
|
||||
}
|
||||
|
||||
public uint Hash(uint data)
|
||||
{
|
||||
uint hash = DefaultHash;
|
||||
hash = HashHelper(hash, (byte)(data >> 0));
|
||||
hash = HashHelper(hash, (byte)(data >> 8));
|
||||
hash = HashHelper(hash, (byte)(data >> 16));
|
||||
hash = HashHelper(hash, (byte)(data >> 24));
|
||||
return hash;
|
||||
}
|
||||
|
||||
public uint Hash(string data, int maxLength = -1)
|
||||
{
|
||||
if (maxLength is -1)
|
||||
{
|
||||
maxLength = data.Length;
|
||||
}
|
||||
|
||||
uint seed1 = WStringHashSeed1;
|
||||
uint seed2 = WStringHashSeed2;
|
||||
uint hash = DefaultHash;
|
||||
|
||||
for(var i = 0; i < maxLength && i < data.Length; i++)
|
||||
{
|
||||
var c = data[i];
|
||||
var uc = (uint)c;
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
uc -= 0x20;
|
||||
}
|
||||
|
||||
seed1 = (seed1 << 3) ^ uc;
|
||||
seed2 += StringHashTable[seed1 & 0xff];
|
||||
hash ^= seed2 + seed1;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ public unsafe sealed class AgentContextService : IAddressHealthService
|
||||
private const int AgentArrayOffset = -0x4;
|
||||
private static readonly byte[] AgentArrayAddressPattern = [0x8b, 0x0c, 0x90, 0x85, 0xc9, 0x74, 0x19];
|
||||
|
||||
private const string PlayerAgentIdMask = "xx????xxxx";
|
||||
private const string PlayerAgentIdMask = "xx????xxxxxxx";
|
||||
private const int PlayerAgentIdOffset = -0xE;
|
||||
private static readonly byte[] PlayerAgentIdAddressPattern = [0x5d, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x55, 0x8b, 0xec, 0x53];
|
||||
private static readonly byte[] PlayerAgentIdAddressPattern = [0x5d, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x55, 0x8b, 0xec, 0x56, 0x57, 0x8b, 0x7d];
|
||||
|
||||
private readonly MemoryScanningService memoryScanningService;
|
||||
private readonly GWAddressCache agentArrayAddress;
|
||||
|
||||
@@ -3,14 +3,23 @@ using Daybreak.API.Interop.GuildWars;
|
||||
using Daybreak.API.Models;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.API.Services.Interop;
|
||||
|
||||
public unsafe sealed class GameContextService : IAddressHealthService
|
||||
{
|
||||
private const string BaseAddressMask = "xxxxxxx";
|
||||
private const int BaseAddressOffset = +7;
|
||||
private static readonly byte[] BaseAddressPattern = [0x50, 0x6A, 0x0F, 0x6A, 0x00, 0xFF, 0x35];
|
||||
// TLS-based pattern to find GameContext getter function
|
||||
private const string GameContextTlsMask = "xx????xxxxxxxxxxx????x";
|
||||
private const int GameContextTlsOffset = 0;
|
||||
private static readonly byte[] GameContextTlsPattern =
|
||||
[
|
||||
0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00, // mov ecx, [TlsIndexPtr] - 6 bytes: xx????
|
||||
0x64, 0xA1, 0x2C, 0x00, 0x00, 0x00, // mov eax, fs:[0000002C] - 6 bytes: xxxxxx
|
||||
0x8B, 0x04, 0x88, // mov eax, [eax+ecx*4] - 3 bytes: xxx
|
||||
0x8B, 0x80, 0x04, 0x00, 0x00, 0x00, // mov eax, [eax+00000004] - 6 bytes: xx????
|
||||
0xC3 // ret - 1 byte: x
|
||||
]; // Total: 22 bytes, mask: 22 chars
|
||||
|
||||
private const string PreGameContextFile = "UiPregame.cpp";
|
||||
private const string PreGameContextAssertion = "!s_scene";
|
||||
@@ -21,7 +30,7 @@ public unsafe sealed class GameContextService : IAddressHealthService
|
||||
private static readonly byte[] AvailableCharsPattern = [0x8B, 0x35, 0x00, 0x00, 0x00, 0x00, 0x57, 0x69, 0xF8, 0x84, 0x00, 0x00, 0x00];
|
||||
|
||||
private readonly MemoryScanningService memoryScanningService;
|
||||
private readonly GWAddressCache baseAddress;
|
||||
private readonly GWAddressCache gameContextGetterAddress;
|
||||
private readonly GWAddressCache preGameContextAddress;
|
||||
private readonly GWAddressCache availableCharsAddress;
|
||||
private readonly ILogger<GameContextService> logger;
|
||||
@@ -32,18 +41,21 @@ public unsafe sealed class GameContextService : IAddressHealthService
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.memoryScanningService = memoryScanningService.ThrowIfNull();
|
||||
this.baseAddress = new GWAddressCache(this.GetBaseAddress);
|
||||
this.gameContextGetterAddress = new GWAddressCache(this.GetGameContextGetterAddress);
|
||||
this.preGameContextAddress = new GWAddressCache(this.GetPreGameContextAddress);
|
||||
this.availableCharsAddress = new GWAddressCache(this.GetAvailableCharactersAddress);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate nuint GetGameContextDelegate();
|
||||
|
||||
public List<AddressState> GetAddressStates()
|
||||
{
|
||||
return [
|
||||
new AddressState
|
||||
{
|
||||
Address = this.baseAddress.GetAddress() ?? 0,
|
||||
Name = nameof(this.baseAddress),
|
||||
Address = this.gameContextGetterAddress.GetAddress() ?? 0,
|
||||
Name = nameof(this.gameContextGetterAddress),
|
||||
},
|
||||
new AddressState
|
||||
{
|
||||
@@ -113,41 +125,46 @@ public unsafe sealed class GameContextService : IAddressHealthService
|
||||
private nuint GetGameContextAddress()
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
if (this.baseAddress.GetAddress() is not nuint basePtr)
|
||||
|
||||
// Get the address of the GameContext getter function
|
||||
var getterAddress = this.gameContextGetterAddress.GetAddress();
|
||||
if (getterAddress is null or 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to get base address");
|
||||
return default;
|
||||
scopedLogger.LogError("Failed to get GameContext getter function address");
|
||||
return 0;
|
||||
}
|
||||
|
||||
nuint** baseContext = *(nuint***)basePtr;
|
||||
if (baseContext is null)
|
||||
// Call the function to get GameContext
|
||||
var getter = Marshal.GetDelegateForFunctionPointer<GetGameContextDelegate>((nint)getterAddress);
|
||||
var gameContextPtr = getter();
|
||||
|
||||
if (gameContextPtr == 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to get base context address");
|
||||
return default;
|
||||
scopedLogger.LogError("GameContext getter returned null");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var globalContextPtr = baseContext[0x6];
|
||||
if (globalContextPtr is null)
|
||||
{
|
||||
scopedLogger.LogError("Failed to get game context address");
|
||||
return default;
|
||||
}
|
||||
|
||||
return (nuint)globalContextPtr;
|
||||
scopedLogger.LogInformation("GameContext address: 0x{address:X8}", gameContextPtr);
|
||||
return gameContextPtr;
|
||||
}
|
||||
|
||||
private nuint GetBaseAddress()
|
||||
private nuint GetGameContextGetterAddress()
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
var baseAddress = this.memoryScanningService.FindAndResolveAddress(BaseAddressPattern, BaseAddressMask, BaseAddressOffset);
|
||||
if (baseAddress is 0)
|
||||
|
||||
var address = this.memoryScanningService.FindAddress(
|
||||
GameContextTlsPattern,
|
||||
GameContextTlsMask,
|
||||
0);
|
||||
|
||||
if (address is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to find base address");
|
||||
scopedLogger.LogError("Failed to find GameContext getter function");
|
||||
return 0U;
|
||||
}
|
||||
|
||||
scopedLogger.LogInformation("Base address: 0x{address:X8}", baseAddress);
|
||||
return baseAddress;
|
||||
scopedLogger.LogInformation("GameContext getter function: 0x{address:X8}", address);
|
||||
return address;
|
||||
}
|
||||
|
||||
private unsafe nuint GetPreGameContextAddress()
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed unsafe class MemoryScanningService(
|
||||
private readonly (nuint BaseAddress, ImageSectionHeader Section) textSection = GetSectionHeader(".text");
|
||||
private readonly (nuint BaseAddress, ImageSectionHeader Section) dataSection = GetSectionHeader(".rdata");
|
||||
|
||||
public uint GameTlsIndex { get; } = GetGameTlsIndex();
|
||||
|
||||
public nuint FunctionFromNearCall(nuint callInstructionAddress, bool checkValidPtr = true)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
@@ -118,7 +120,7 @@ public sealed unsafe class MemoryScanningService(
|
||||
|
||||
fileSearchPos = filePtr + 1;
|
||||
|
||||
// If what we found is “…/file.hpp” (no drive letter) fix it
|
||||
// If what we found is "…/file.hpp" (no drive letter) fix it
|
||||
if (Marshal.ReadByte((nint)(filePtr + 1)) != (byte)':')
|
||||
{
|
||||
// look ≤128 bytes backward for ':' and back-up one char
|
||||
@@ -199,6 +201,12 @@ public sealed unsafe class MemoryScanningService(
|
||||
return address;
|
||||
}
|
||||
|
||||
public nuint FindAddress(byte[] pattern, string mask, int offset, nuint startAddress, nuint endAddress)
|
||||
{
|
||||
var address = FindAddressInternal(startAddress, endAddress, pattern, mask, offset);
|
||||
return address;
|
||||
}
|
||||
|
||||
public nuint ToFunctionStart(nuint callInstructionAddress, uint scanRange = 0x500)
|
||||
{
|
||||
if (callInstructionAddress == 0)
|
||||
@@ -216,6 +224,92 @@ public sealed unsafe class MemoryScanningService(
|
||||
return FindInRange(prologue, mask, 0, start, end);
|
||||
}
|
||||
|
||||
public nuint FindNthUseOfString(string str, uint nth, int offset = 0, bool useTextSection = true)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
var strBytes = Encoding.ASCII.GetBytes(str);
|
||||
var mask = new string('x', strBytes.Length);
|
||||
|
||||
var foundStr = FindAddressInternal(this.dataSection, strBytes, mask, 0);
|
||||
if (foundStr is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to find string in .rdata section");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var firstNullChar = FindInRange([0x0], "x", 1, foundStr, foundStr - 0x64);
|
||||
if (firstNullChar is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to find null terminator");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.FindNthUseOfAddress(firstNullChar, nth, offset, useTextSection);
|
||||
}
|
||||
|
||||
public nuint FindUseOfString(string str, int offset = 0, bool useTextSection = true)
|
||||
{
|
||||
return this.FindNthUseOfString(str, 0, offset, useTextSection);
|
||||
}
|
||||
|
||||
public nuint FindNthUseOfWideString(string str, uint nth, int offset = 0, bool useTextSection = true)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
var strBytes = Encoding.Unicode.GetBytes(str);
|
||||
var mask = new string('x', strBytes.Length);
|
||||
|
||||
var foundStr = FindAddressInternal(this.dataSection, strBytes, mask, 0);
|
||||
if (foundStr is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to find wide string in .rdata section");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var firstNullChar = FindInRange([0x0, 0x0], "xx", 2, foundStr, foundStr - 0x128);
|
||||
if (firstNullChar is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to find null terminator for wide string");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.FindNthUseOfAddress(firstNullChar, nth, offset, useTextSection);
|
||||
}
|
||||
|
||||
public nuint FindUseOfWideString(string str, int offset = 0, bool useTextSection = true)
|
||||
{
|
||||
return this.FindNthUseOfWideString(str, 0, offset, useTextSection);
|
||||
}
|
||||
|
||||
private nuint FindNthUseOfAddress(nuint address, uint nth, int offset, bool useTextSection)
|
||||
{
|
||||
var (BaseAddress, Section) = useTextSection ? this.textSection : this.dataSection;
|
||||
var addressBytes = BitConverter.GetBytes((uint)address);
|
||||
var mask = "xxxx";
|
||||
|
||||
var currentAddress = BaseAddress + Section.VirtualAddress;
|
||||
var endAddress = currentAddress + Section.VirtualSize;
|
||||
|
||||
uint foundCount = 0;
|
||||
while (currentAddress < endAddress)
|
||||
{
|
||||
var found = FindAddressInternal(currentAddress, endAddress, addressBytes, mask, 0);
|
||||
if (found is 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundCount == nth)
|
||||
{
|
||||
return (nuint)((nint)found + offset);
|
||||
}
|
||||
|
||||
foundCount++;
|
||||
currentAddress = found + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool IsValidPtr(nuint address, (nuint BaseAddress, ImageSectionHeader Section) section)
|
||||
{
|
||||
return address > 0 &&
|
||||
@@ -276,8 +370,12 @@ public sealed unsafe class MemoryScanningService(
|
||||
{
|
||||
var textStart = section.BaseAddress + section.Section.VirtualAddress;
|
||||
var textEnd = textStart + section.Section.VirtualSize - (uint)pattern.Length;
|
||||
return FindAddressInternal(textStart, textEnd, pattern, mask, offset);
|
||||
}
|
||||
|
||||
for (var cur = textStart; cur <= textEnd; ++cur)
|
||||
private static nuint FindAddressInternal(nuint startAddr, nuint endAddr, byte[] pattern, string mask, int offset = 0)
|
||||
{
|
||||
for (var cur = startAddr; cur <= endAddr; ++cur)
|
||||
{
|
||||
if (Marshal.ReadByte((nint)cur) != pattern[0])
|
||||
{
|
||||
@@ -312,4 +410,30 @@ public sealed unsafe class MemoryScanningService(
|
||||
? throw new InvalidOperationException($"Failed to initialize memory scanner. Failed to find {headerName} section")
|
||||
: ((nuint, ImageSectionHeader))(baseAddr, textHdr);
|
||||
}
|
||||
|
||||
private static uint GetGameTlsIndex()
|
||||
{
|
||||
var m = Process.GetCurrentProcess().MainModule ?? throw new InvalidOperationException("Failed to get TLS index. Failed to find main module");
|
||||
var baseAddr = (nint)m.BaseAddress;
|
||||
var pe = new PeFile(m.FileName);
|
||||
|
||||
// Get the TLS data directory entry (index 9 = IMAGE_DIRECTORY_ENTRY_TLS)
|
||||
var tlsDirectory = pe.ImageNtHeaders?.OptionalHeader.DataDirectory[9];
|
||||
if (tlsDirectory is null || tlsDirectory.VirtualAddress == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get TLS index. No TLS directory found");
|
||||
}
|
||||
|
||||
// Calculate the address of the TLS directory in memory
|
||||
var tlsDirectoryAddress = baseAddr + (nint)tlsDirectory.VirtualAddress;
|
||||
|
||||
// Read the IMAGE_TLS_DIRECTORY structure
|
||||
// AddressOfIndex is at offset 8 (after StartAddressOfRawData and EndAddressOfRawData)
|
||||
var addressOfIndexPtr = Marshal.ReadIntPtr(tlsDirectoryAddress + 8);
|
||||
|
||||
// Read the TLS index from the location pointed to by AddressOfIndex
|
||||
var tlsIndex = (uint)Marshal.ReadInt32(addressOfIndexPtr);
|
||||
|
||||
return tlsIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Daybreak.API.Models;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using static Daybreak.API.Models.UIPackets;
|
||||
|
||||
namespace Daybreak.API.Services.Interop;
|
||||
|
||||
@@ -10,13 +11,12 @@ public sealed class PartyContextService
|
||||
{
|
||||
private const string SearchButtonFile = "\\Code\\Gw\\Ui\\Game\\Party\\PtSearch.cpp";
|
||||
private const string SearchButtonAssertion = "m_activeList == LIST_HEROES";
|
||||
private const string WindowButtonFile = "\\Code\\Gw\\Ui\\Game\\Party\\PtButtons.cpp";
|
||||
private const string WindowButtonAssertion = "m_selection.agentId";
|
||||
private const string WindowButtonAssertion = "selection.agentId";
|
||||
|
||||
// Fastcall funcs do not work in .NET for x86 right now. https://github.com/dotnet/runtime/issues/113851
|
||||
// This fastcall equivalent is <void*, uint, uint*, void>
|
||||
// This fastcall equivalent is <void*, uint, UIPacket::kMouseAction*, void>
|
||||
private readonly GWFastCall<nint, uint, nint, GWFastCall.Void> searchButtonCallback;
|
||||
// This fastcall equivalent is <void*, uint, uint*, void>
|
||||
// This fastcall equivalent is <void*, uint, UIPacket::kMouseAction*, void>
|
||||
private readonly GWFastCall<nint, uint, nint, GWFastCall.Void> windowButtonCallback;
|
||||
|
||||
private readonly MemoryScanningService memoryScanningService;
|
||||
@@ -29,7 +29,7 @@ public sealed class PartyContextService
|
||||
this.memoryScanningService = memoryScanningService.ThrowIfNull();
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.searchButtonCallback = new(new GWAddressCache(() => this.memoryScanningService.ToFunctionStart(this.memoryScanningService.FindAssertion(SearchButtonFile, SearchButtonAssertion, 0, 0))));
|
||||
this.windowButtonCallback = new(new GWAddressCache(() => this.memoryScanningService.ToFunctionStart(this.memoryScanningService.FindAssertion(WindowButtonFile, WindowButtonAssertion, 0, 0))));
|
||||
this.windowButtonCallback = new(new GWAddressCache(() => this.memoryScanningService.ToFunctionStart(this.memoryScanningService.FindUseOfString(WindowButtonAssertion))));
|
||||
}
|
||||
|
||||
public List<AddressState> GetAddressStates()
|
||||
@@ -52,13 +52,13 @@ public sealed class PartyContextService
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool CallSearchButtonCallback(void* ctx, uint edx, uint* wparam)
|
||||
public unsafe bool CallSearchButtonCallback(void* ctx, uint edx, MouseAction* wparam)
|
||||
{
|
||||
this.searchButtonCallback.Invoke((nint)ctx, edx, (nint)wparam);
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe bool CallWindowButtonCallback(void* ctx, uint edx, uint* wparam)
|
||||
public unsafe bool CallWindowButtonCallback(void* ctx, uint edx, MouseAction* wparam)
|
||||
{
|
||||
this.windowButtonCallback.Invoke((nint)ctx, edx, (nint)wparam);
|
||||
return true;
|
||||
@@ -66,26 +66,28 @@ public sealed class PartyContextService
|
||||
|
||||
public unsafe bool AddHero(uint heroId)
|
||||
{
|
||||
var wparam = stackalloc uint[4];
|
||||
wparam[2] = 0x6;
|
||||
wparam[1] = 0x1;
|
||||
var action = new MouseAction(
|
||||
frameId: 0,
|
||||
childOffsetId: 0x1,
|
||||
currentState: ActionState.MouseUp);
|
||||
|
||||
var ctx = stackalloc uint[13];
|
||||
ctx[0xb] = 1;
|
||||
ctx[9] = heroId;
|
||||
return this.CallSearchButtonCallback(ctx, 2, wparam);
|
||||
ctx[0x9] = heroId;
|
||||
return this.CallSearchButtonCallback(ctx, 2, &action);
|
||||
}
|
||||
|
||||
public unsafe bool KickHero(uint heroId)
|
||||
{
|
||||
var wparam = stackalloc uint[4];
|
||||
wparam[2] = 0x6;
|
||||
wparam[1] = 0x6;
|
||||
var action = new MouseAction(
|
||||
frameId: 0,
|
||||
childOffsetId: 0x6,
|
||||
currentState: ActionState.MouseUp);
|
||||
|
||||
var ctx = stackalloc uint[13];
|
||||
ctx[0xb] = 1;
|
||||
ctx[9] = heroId;
|
||||
return this.CallSearchButtonCallback(ctx, 0, wparam);
|
||||
ctx[0x9] = heroId;
|
||||
return this.CallSearchButtonCallback(ctx, 0, &action);
|
||||
}
|
||||
|
||||
public bool KickAllHeroes() => this.KickHero(0x26);
|
||||
@@ -94,6 +96,6 @@ public sealed class PartyContextService
|
||||
{
|
||||
var ctx = stackalloc uint[14];
|
||||
ctx[0xd] = 1;
|
||||
return this.CallWindowButtonCallback(ctx, 0, (uint*)0);
|
||||
return this.CallWindowButtonCallback(ctx, 0, (MouseAction*)0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Daybreak.API.Interop;
|
||||
using Daybreak.API.Interop.GuildWars;
|
||||
using Daybreak.API.Models;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -13,28 +14,30 @@ public sealed class UIContextService
|
||||
{
|
||||
private const string GetChildFrameIdFile = "CtlView.cpp";
|
||||
private const string GetChildFrameIdAssertion = "pageId";
|
||||
private const int GetChildFrameIdOffset = 0x19;
|
||||
private const string SetFrameFlagFile = "FrApi.cpp";
|
||||
private const string SetFrameFlagAssertion = "frameId";
|
||||
private const int SetFrameVisibleOffset = 0x4f9;
|
||||
private const int SetFrameDisabledOffset = 0x4e3;
|
||||
private static readonly byte[] SendFrameUiMessageByIdSeq = [0x83, 0xFB, 0x47, 0x73, 0x14];
|
||||
private const string SendFrameUiMessageByIdMask = "xxxxx";
|
||||
private const int SendFrameUiMessageByIdOffset = -0x34;
|
||||
private const int GetChildFrameIdOffset = 0x12;
|
||||
private static readonly byte[] GetChildFrameIdPattern = [0xE8];
|
||||
private const string GetChildFrameIdMask = "x";
|
||||
|
||||
private static readonly byte[] SendFrameUiMessageSeq = [
|
||||
0x83, 0xC1, 0xDC, // add ecx, -24
|
||||
0xE8, 0x00, 0x00, 0x00, 0x00, // call
|
||||
0x8B, 0x4D, 0xFC // mov ecx, [ebp-04]
|
||||
];
|
||||
private const string SendFrameUiMessageMask = "xxxx????xxx";
|
||||
private const int SendFrameUiMessageOffset = 0x3;
|
||||
|
||||
private static readonly byte[] SendUIMessageSeq = [0xB9, 0x00, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xC3, 0x89, 0x45, 0x08];
|
||||
private const string SendUIMessageMask = "x????x????xxxxx";
|
||||
private const string CreateHashFromStringMask = "xxxxxxx";
|
||||
private const int CreateHashFromStringOffset = 0x7;
|
||||
private static readonly byte[] CreateHashFromStringSeq = [0x85, 0xC0, 0x74, 0x0D, 0x6A, 0xFF, 0x50];
|
||||
|
||||
private const string FrameArrayFile = "\\Code\\Engine\\Frame\\FrMsg.cpp";
|
||||
private const string FrameArrayAssertion = "frame";
|
||||
private static readonly byte[] GetRootFrameSeq = [0x05, 0xE0, 0xFE, 0xFF, 0xFF, 0xC3];
|
||||
private const string GetRootFrameMask = "xxxxxx";
|
||||
private const int GetRootFrameOffset = -0x3C;
|
||||
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private unsafe delegate uint CreateHashFromStringFunc(char* value, int seed);
|
||||
|
||||
private static readonly byte[] GetRootFrameSeq = [
|
||||
0xA1, 0x00, 0x00, 0x00, 0x00, // mov eax,[address]
|
||||
0x05, 0xD8, 0xFE, 0xFF, 0xFF, // add eax,FFFFFED8
|
||||
0xC3 // ret
|
||||
];
|
||||
private const string GetRootFrameMask = "x????xxxxxx";
|
||||
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
@@ -54,7 +57,7 @@ public sealed class UIContextService
|
||||
|
||||
private readonly SemaphoreSlim semaphoreSlim = new(1);
|
||||
private readonly MemoryScanningService memoryScanningService;
|
||||
private readonly GWDelegateCache<CreateHashFromStringFunc> createHashFromString;
|
||||
private readonly HashingService hashingService;
|
||||
private readonly GWDelegateCache<GetChildFrameIdFunc> getChildFrameId;
|
||||
private readonly GWAddressCache frameArrayAddressCache;
|
||||
private readonly GWHook<SendUIMessageFunc> sendUiMessageHook;
|
||||
@@ -66,13 +69,13 @@ public sealed class UIContextService
|
||||
private CancellationTokenSource? cts = default;
|
||||
|
||||
public UIContextService(
|
||||
HashingService hashingService,
|
||||
MemoryScanningService memoryScanningService,
|
||||
ILogger<UIContextService> logger)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.memoryScanningService = memoryScanningService.ThrowIfNull();
|
||||
this.createHashFromString = new GWDelegateCache<CreateHashFromStringFunc>(new GWAddressCache(() => this.memoryScanningService.FunctionFromNearCall(
|
||||
this.memoryScanningService.FindAddress(CreateHashFromStringSeq, CreateHashFromStringMask, CreateHashFromStringOffset))));
|
||||
this.hashingService = hashingService.ThrowIfNull();
|
||||
this.frameArrayAddressCache = new GWAddressCache(() => this.memoryScanningService.FindAssertion(FrameArrayFile, FrameArrayAssertion, 0, -0x14));
|
||||
this.sendUiMessageHook = new GWHook<SendUIMessageFunc>(
|
||||
new GWAddressCache(() => this.memoryScanningService.ToFunctionStart(
|
||||
@@ -80,23 +83,24 @@ public sealed class UIContextService
|
||||
this.OnSendUIMessage);
|
||||
this.sendFrameUIMessage = new(
|
||||
new GWAddressCache(() => this.memoryScanningService.FunctionFromNearCall(
|
||||
this.memoryScanningService.FindAddress(SendFrameUiMessageByIdSeq, SendFrameUiMessageByIdMask, SendFrameUiMessageByIdOffset) + 0x67)));
|
||||
this.memoryScanningService.FindAddress(SendFrameUiMessageSeq, SendFrameUiMessageMask, SendFrameUiMessageOffset))));
|
||||
this.getChildFrameId = new GWDelegateCache<GetChildFrameIdFunc>(
|
||||
new GWAddressCache(() => this.memoryScanningService.FunctionFromNearCall(
|
||||
this.memoryScanningService.FindAssertion(GetChildFrameIdFile, GetChildFrameIdAssertion, 0, GetChildFrameIdOffset))));
|
||||
new GWAddressCache(() =>
|
||||
{
|
||||
var address = this.memoryScanningService.FindAssertion(GetChildFrameIdFile, GetChildFrameIdAssertion, 0, GetChildFrameIdOffset);
|
||||
return this.memoryScanningService.FunctionFromNearCall(
|
||||
this.memoryScanningService.FindAddress(GetChildFrameIdPattern, GetChildFrameIdMask, 0, address, address + 0xFF));
|
||||
}));
|
||||
this.getRootFrame = new GWDelegateCache<GetRootFrameFunc>(
|
||||
new GWAddressCache(() => this.memoryScanningService.FindAddress(GetRootFrameSeq, GetRootFrameMask, GetRootFrameOffset)));
|
||||
new GWAddressCache(() => this.memoryScanningService.FindAddress(GetRootFrameSeq, GetRootFrameMask, 0)));
|
||||
}
|
||||
|
||||
public List<AddressState> GetAddressStates()
|
||||
{
|
||||
var assertion = this.memoryScanningService.FindAssertion(GetChildFrameIdFile, GetChildFrameIdAssertion, 0, GetChildFrameIdOffset);
|
||||
Console.WriteLine(assertion);
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = nameof(this.createHashFromString),
|
||||
Address = this.createHashFromString.Cache.GetAddress() ?? 0U
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = nameof(this.frameArrayAddressCache),
|
||||
@@ -104,13 +108,13 @@ public sealed class UIContextService
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = nameof(this.getChildFrameId),
|
||||
Address = this.getChildFrameId.Cache.GetAddress() ?? 0U
|
||||
Name = nameof(this.sendFrameUIMessage),
|
||||
Address = this.sendFrameUIMessage.Cache.GetAddress() ?? 0U
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = nameof(this.sendFrameUIMessage),
|
||||
Address = this.sendFrameUIMessage.Cache.GetAddress() ?? 0U
|
||||
Name = nameof(this.getChildFrameId),
|
||||
Address = this.getChildFrameId.Cache.GetAddress() ?? 0U
|
||||
},
|
||||
new()
|
||||
{
|
||||
@@ -268,7 +272,7 @@ public sealed class UIContextService
|
||||
return null;
|
||||
}
|
||||
|
||||
var hash = this.CreateHashFromString(label, -1);
|
||||
var hash = this.CreateHashFromString(label);
|
||||
if (hash is 0)
|
||||
{
|
||||
scopedLogger.LogError("Failed to get hash for label {label}", label);
|
||||
@@ -315,19 +319,9 @@ public sealed class UIContextService
|
||||
return frame;
|
||||
}
|
||||
|
||||
public unsafe uint CreateHashFromString(string value, int seed)
|
||||
public uint CreateHashFromString(string value)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
if (this.createHashFromString.GetDelegate() is not CreateHashFromStringFunc del)
|
||||
{
|
||||
scopedLogger.LogError("Failed to get CreateHashFromString delegate");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fixed (char* valuePtr = value)
|
||||
{
|
||||
return del(valuePtr, seed);
|
||||
}
|
||||
return this.hashingService.Hash(value);
|
||||
}
|
||||
|
||||
public unsafe void SendMessage(UIMessage message, nuint wParam, nuint lParam)
|
||||
@@ -357,24 +351,24 @@ public sealed class UIContextService
|
||||
|
||||
private static unsafe void SetFrameVisibleInternal(WrappedPointer<Frame> frame, bool visible)
|
||||
{
|
||||
uint* pState = &frame.Pointer->Field91; // direct field address
|
||||
uint* pState = &frame.Pointer->FrameState; // direct field address
|
||||
if (visible)
|
||||
{
|
||||
*pState &= ~0x200u; // clear “hidden”
|
||||
*pState &= ~0x200u; // clear "hidden"
|
||||
}
|
||||
else
|
||||
{
|
||||
*pState |= 0x200u; // set “hidden”
|
||||
*pState |= 0x200u; // set "hidden"
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetFrameDisabledInternal(WrappedPointer<Frame> frame, bool disabled)
|
||||
{
|
||||
uint* pState = &frame.Pointer->Field91; // same word (offset 0x184)
|
||||
uint* pState = &frame.Pointer->FrameState; // same word (offset 0x184)
|
||||
|
||||
if (disabled)
|
||||
{
|
||||
*pState |= 0x10u; // set “disabled”
|
||||
*pState |= 0x10u; // set "disabled"
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using Daybreak.Shared.Services.BuildTemplates;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
using System.Windows.Controls;
|
||||
using ZLinq;
|
||||
using InstanceType = Daybreak.API.Interop.GuildWars.InstanceType;
|
||||
|
||||
@@ -47,7 +48,7 @@ public sealed class PartyService(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await this.LeaveParty(cancellationToken))
|
||||
if (!await this.KickAllHeroes(cancellationToken))
|
||||
{
|
||||
scopedLogger.LogError("Could not set party loadout. Could not leave party");
|
||||
return false;
|
||||
@@ -67,14 +68,15 @@ public sealed class PartyService(
|
||||
return false;
|
||||
}
|
||||
|
||||
var heroBehaviorSetup = await this.gameThreadService.QueueOnGameThread(() => this.GetHeroBehaviorSetup(partyLoadout), cancellationToken);
|
||||
foreach(var heroBehaviorEntry in heroBehaviorSetup ?? [])
|
||||
{
|
||||
if (!await this.SetHeroBehavior(heroBehaviorEntry.AgentId, heroBehaviorEntry.Behavior, cancellationToken))
|
||||
{
|
||||
scopedLogger.LogWarning("Could not set hero behavior for agent {agentId} to {behavior}", heroBehaviorEntry.AgentId, heroBehaviorEntry.Behavior);
|
||||
}
|
||||
}
|
||||
// TODO: Hashing is broken
|
||||
//var heroBehaviorSetup = await this.gameThreadService.QueueOnGameThread(() => this.GetHeroBehaviorSetup(partyLoadout), cancellationToken);
|
||||
//foreach (var heroBehaviorEntry in heroBehaviorSetup ?? [])
|
||||
//{
|
||||
// if (!await this.SetHeroBehavior(heroBehaviorEntry.AgentId, heroBehaviorEntry.Behavior, cancellationToken))
|
||||
// {
|
||||
// scopedLogger.LogWarning("Could not set hero behavior for agent {agentId} to {behavior}", heroBehaviorEntry.AgentId, heroBehaviorEntry.Behavior);
|
||||
// }
|
||||
//}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -143,7 +145,7 @@ public sealed class PartyService(
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> LeaveParty(CancellationToken cancellationToken)
|
||||
public async Task<bool> KickAllHeroes(CancellationToken cancellationToken)
|
||||
{
|
||||
var partySize = await this.GetPartySize(cancellationToken);
|
||||
if (partySize is 1 or 0)
|
||||
@@ -151,7 +153,7 @@ public sealed class PartyService(
|
||||
return true;
|
||||
}
|
||||
|
||||
return await this.gameThreadService.QueueOnGameThread(this.partyContextService.LeaveParty, cancellationToken);
|
||||
return await this.gameThreadService.QueueOnGameThread(this.partyContextService.KickAllHeroes, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<uint> GetPartySize(CancellationToken cancellationToken)
|
||||
|
||||
@@ -9,6 +9,7 @@ public static class WebApplicationBuilderExtensions
|
||||
{
|
||||
public static WebApplicationBuilder WithDaybreakServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<HashingService>();
|
||||
builder.Services.AddSingleton<IBuildTemplateManager, BuildTemplateManager>();
|
||||
builder.Services.AddSingleton<IMDomainNameService, MDomainNameService>();
|
||||
builder.Services.AddHostedService<ApiAdvertisingService>();
|
||||
|
||||
Reference in New Issue
Block a user