mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
162 lines
5.5 KiB
C#
162 lines
5.5 KiB
C#
using Daybreak.API.Interop;
|
|
using Daybreak.API.Interop.GuildWars;
|
|
using Daybreak.API.Models;
|
|
using System.Collections.Concurrent;
|
|
using System.Core.Extensions;
|
|
using System.Extensions.Core;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
|
|
namespace Daybreak.API.Services.Interop;
|
|
|
|
public sealed class UIContextService(ILogger<UIContextService> logger)
|
|
{
|
|
[SuppressUnmanagedCodeSecurity]
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private unsafe delegate void AsyncDecodeStrCallback(void* param, ushort* decodedString);
|
|
|
|
// Prevent GC of delegates passed to native code
|
|
private readonly ConcurrentDictionary<AsyncDecodeStrCallback, bool> prevent_GC_callbacks = [];
|
|
|
|
private readonly ILogger<UIContextService> logger = logger.ThrowIfNull();
|
|
|
|
public unsafe WrappedPointer<T> GetFrameContext<T>(WrappedPointer<Frame> frame)
|
|
where T : unmanaged
|
|
{
|
|
if (frame.IsNull || frame.Pointer->FrameCallbacks.Size == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (uint i = 0; i < frame.Pointer->FrameCallbacks.Size; i++)
|
|
{
|
|
var callback = frame.Pointer->FrameCallbacks.Buffer[i];
|
|
if (callback.UiCtl_Context != null)
|
|
{
|
|
return new WrappedPointer<T>((T*)callback.UiCtl_Context);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public unsafe WrappedPointer<Frame> GetChildFrame(WrappedPointer<Frame> parent, uint childOffset)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
if (parent.IsNull)
|
|
{
|
|
scopedLogger.LogError("Parent frame is null");
|
|
return null;
|
|
}
|
|
|
|
return GWCA.GW.UI.GetChildFrame(parent, childOffset);
|
|
}
|
|
|
|
public WrappedPointer<Frame> GetButtonActionFrame()
|
|
{
|
|
return this.GetChildFrame(this.GetFrameByLabel("Game"), 6);
|
|
}
|
|
|
|
public unsafe bool SendFrameUIMessage(WrappedPointer<Frame> frame, UIMessage messageId, void* arg1, void* arg2 = null)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
if (frame.IsNull)
|
|
{
|
|
scopedLogger.LogError("Frame is null");
|
|
return false;
|
|
}
|
|
|
|
return GWCA.GW.UI.SendFrameUIMessage(frame, messageId, arg1, (nint)arg2);
|
|
}
|
|
|
|
public unsafe bool SetFrameDisabled(WrappedPointer<Frame> frame, bool disabled) => GWCA.GW.UI.SetFrameDisabled(frame, disabled);
|
|
|
|
public unsafe bool SetFrameVisible(WrappedPointer<Frame> frame, bool visible) => GWCA.GW.UI.SetFrameVisible(frame, visible);
|
|
|
|
public unsafe bool KeyDown(ControlAction action, WrappedPointer<Frame> frame) => GWCA.GW.UI.Keydown(action, frame);
|
|
|
|
public unsafe bool KeyUp(ControlAction action, WrappedPointer<Frame> frame) => GWCA.GW.UI.Keyup(action, frame);
|
|
|
|
public unsafe WrappedPointer<Frame> GetFrameByLabel(string label)
|
|
{
|
|
fixed (char* labelPtr = label)
|
|
{
|
|
return GWCA.GW.UI.GetFrameByLabel((ushort*)labelPtr);
|
|
}
|
|
}
|
|
|
|
public unsafe WrappedPointer<Frame> GetFrameById(uint frameId) => GWCA.GW.UI.GetFrameById(frameId);
|
|
|
|
public unsafe WrappedPointer<Frame> GetParentFrame(WrappedPointer<Frame> frame)
|
|
{
|
|
if (frame.IsNull)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return GWCA.GW.UI.GetParentFrame(frame);
|
|
}
|
|
|
|
public unsafe bool ButtonClick(WrappedPointer<Frame> frame)
|
|
{
|
|
if (frame.IsNull)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return GWCA.GW.UI.ButtonClick(frame);
|
|
}
|
|
|
|
public unsafe void SendMessage(UIMessage message, nuint wParam, nuint lParam) => GWCA.GW.UI.SendUIMessage(message, (void*)wParam, (nint)lParam);
|
|
|
|
public unsafe Task<string> AsyncDecodeStringAsync(ushort* encodedString, Language language = Language.Unknown, CancellationToken cancellationToken = default)
|
|
{
|
|
var taskCompletionSource = new TaskCompletionSource<string>();
|
|
cancellationToken.Register(() => taskCompletionSource.TrySetCanceled(cancellationToken));
|
|
void callback(void* param, ushort* decodedString)
|
|
{
|
|
try
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
taskCompletionSource.TrySetCanceled(cancellationToken);
|
|
return;
|
|
}
|
|
|
|
// Convert the decoded wide string to a managed string
|
|
var result = decodedString != null
|
|
? new string((char*)decodedString)
|
|
: string.Empty;
|
|
|
|
taskCompletionSource.TrySetResult(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
taskCompletionSource.TrySetException(ex);
|
|
}
|
|
finally
|
|
{
|
|
this.prevent_GC_callbacks.TryRemove(callback, out _);
|
|
}
|
|
}
|
|
|
|
// CRITICAL: Keep delegate alive until callback executes
|
|
this.prevent_GC_callbacks[callback] = true;
|
|
|
|
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
|
|
// GWCA's AsyncDecodeStr handles language switching internally (sets language, decodes, restores)
|
|
GWCA.GW.UI.AsyncDecodeStr(encodedString, funcPtr, 0, language);
|
|
|
|
return taskCompletionSource.Task;
|
|
}
|
|
|
|
public unsafe Task<string> AsyncDecodeStringAsync(string encodedString, Language language = Language.Unknown, CancellationToken cancellationToken = default)
|
|
{
|
|
// Pin the string and convert to ushort*
|
|
fixed (char* encodedPtr = encodedString)
|
|
{
|
|
return this.AsyncDecodeStringAsync((ushort*)encodedPtr, language, cancellationToken);
|
|
}
|
|
}
|
|
}
|