mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
@@ -34,9 +34,9 @@ public class EntryPoint
|
||||
{
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", null, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("ASPNETCORE_PREVENTHOSTINGSTARTUP", "true", EnvironmentVariableTarget.Process);
|
||||
|
||||
#if DEBUG
|
||||
ConsoleExtensions.AllocateAnsiConsole();
|
||||
|
||||
#endif
|
||||
var port = FindAvailablePort(StartPort);
|
||||
var app = CreateApplication(port);
|
||||
var runTask = Task.Run(() => StartServer(app), CancellationTokenSource.Token);
|
||||
|
||||
@@ -128,11 +128,11 @@ internal static unsafe partial class GWCA
|
||||
|
||||
// GW::RegisterLogHandler
|
||||
[LibraryImport(DllName, EntryPoint = "?RegisterLogHandler@GW@@YAXP6AXPAXW4LogLevel@1@PBD2I2@Z0@Z")]
|
||||
internal static partial void RegisterLogHandler(nint callback);
|
||||
internal static partial void RegisterLogHandler(nint callback1, nint ptr2);
|
||||
|
||||
// GW::RegisterPanicHandler
|
||||
[LibraryImport(DllName, EntryPoint = "?RegisterPanicHandler@GW@@YAXP6AXPAXPBD1I1@Z0@Z")]
|
||||
internal static partial void RegisterPanicHandler(nint callback);
|
||||
internal static partial void RegisterPanicHandler(nint callback1, nint ptr2);
|
||||
|
||||
// GW::Terminate
|
||||
[LibraryImport(DllName, EntryPoint = "?Terminate@GW@@YAXXZ")]
|
||||
@@ -2041,7 +2041,7 @@ internal static unsafe partial class GWCA
|
||||
// GW::TabsFrame::AddTab
|
||||
[LibraryImport(DllName, EntryPoint = "?AddTab@TabsFrame@GW@@QAEPAUFrame@UI@2@PB_WIIP6AXPAUInteractionMessage@42@PAX2@Z2@Z")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvThiscall)])]
|
||||
internal static partial global::Daybreak.API.Interop.GuildWars.Frame* AddTab(nint self, ushort* ptr2, uint value3, uint value4, nint callback5);
|
||||
internal static partial global::Daybreak.API.Interop.GuildWars.Frame* AddTab(nint self, ushort* ptr2, uint value3, uint value4, nint callback5, nint ptr6);
|
||||
|
||||
// GW::TabsFrame::ChooseTab
|
||||
[LibraryImport(DllName, EntryPoint = "?ChooseTab@TabsFrame@GW@@QAE_NI@Z")]
|
||||
@@ -2171,11 +2171,11 @@ internal static unsafe partial class GWCA
|
||||
// GW::UI::AddFrameUIInteractionCallback
|
||||
[LibraryImport(DllName, EntryPoint = "?AddFrameUIInteractionCallback@UI@GW@@YA_NPAUFrame@12@P6AXPAUInteractionMessage@12@PAX2@Z2@Z")]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool AddFrameUIInteractionCallback(global::Daybreak.API.Interop.GuildWars.Frame* frame1, nint callback2);
|
||||
internal static partial bool AddFrameUIInteractionCallback(global::Daybreak.API.Interop.GuildWars.Frame* frame1, nint callback2, nint ptr3);
|
||||
|
||||
// GW::UI::AsyncDecodeStr
|
||||
[LibraryImport(DllName, EntryPoint = "?AsyncDecodeStr@UI@GW@@YAXPB_WP6AXPAX0@Z1W4Language@Constants@2@@Z")]
|
||||
internal static partial void AsyncDecodeStr(ushort* ptr1, nint callback2);
|
||||
internal static partial void AsyncDecodeStr(ushort* ptr1, nint callback2, nint ptr3, global::Daybreak.API.Interop.GuildWars.Language language4);
|
||||
|
||||
// GW::UI::AsyncDecodeStr
|
||||
[LibraryImport(DllName, EntryPoint = "?AsyncDecodeStr@UI@GW@@YAXPB_WPA_WI@Z")]
|
||||
@@ -2188,7 +2188,7 @@ internal static unsafe partial class GWCA
|
||||
|
||||
// GW::UI::CreateUIComponent
|
||||
[LibraryImport(DllName, EntryPoint = "?CreateUIComponent@UI@GW@@YAIIIIP6AXPAUInteractionMessage@12@PAX1@Z1PB_W@Z")]
|
||||
internal static partial uint CreateUIComponent(uint value1, uint value2, uint value3, nint callback4);
|
||||
internal static partial uint CreateUIComponent(uint value1, uint value2, uint value3, nint callback4, nint ptr5, ushort* ptr6);
|
||||
|
||||
// GW::UI::Default_UICallback | interactionMessage1: TODO: map struct GW::UI::InteractionMessage
|
||||
// [LibraryImport(DllName, EntryPoint = "?Default_UICallback@UI@GW@@YA_NPAUInteractionMessage@12@PAX1@Z")]
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed class UIContextService(ILogger<UIContextService> logger)
|
||||
|
||||
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, CancellationToken cancellationToken = default)
|
||||
public unsafe Task<string> AsyncDecodeStringAsync(ushort* encodedString, Language language = Language.Unknown, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<string>();
|
||||
cancellationToken.Register(() => taskCompletionSource.TrySetCanceled(cancellationToken));
|
||||
@@ -144,17 +144,18 @@ public sealed class UIContextService(ILogger<UIContextService> logger)
|
||||
this.prevent_GC_callbacks[callback] = true;
|
||||
|
||||
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
|
||||
GWCA.GW.UI.AsyncDecodeStr(encodedString, funcPtr);
|
||||
// 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, CancellationToken cancellationToken = default)
|
||||
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, cancellationToken);
|
||||
return this.AsyncDecodeStringAsync((ushort*)encodedPtr, language, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +83,21 @@ public sealed class InventoryService(
|
||||
return default;
|
||||
}
|
||||
|
||||
var decodedItemTuples = await Task.WhenAll(itemTuples.Select(async tuple =>
|
||||
// Decode strings sequentially to avoid race conditions with the game's TextParser language field.
|
||||
// Parallel decoding causes crashes because multiple operations race to set/restore the language.
|
||||
var decodedItemTuples = new List<(BagType Type, List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType)> Items)>();
|
||||
foreach (var tuple in itemTuples)
|
||||
{
|
||||
var decodedItems = await Task.WhenAll(tuple.Item2.Select(async item =>
|
||||
var decodedItems = new List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType)>();
|
||||
foreach (var item in tuple.Item2)
|
||||
{
|
||||
var decodedName = await this.uIService.DecodeString(item.EncodedName, Language.English, cancellationToken);
|
||||
var decodedCompleteName = await this.uIService.DecodeString(item.EncodedCompleteName, Language.English, cancellationToken);
|
||||
var decodedSingleName = await this.uIService.DecodeString(item.EncodedSingleName, Language.English, cancellationToken);
|
||||
return (item.ModelId, item.EncodedName, DecodedName: decodedName, item.EncodedSingleName, DecodedSingleName: decodedSingleName, item.EncodedCompleteName, DecodedCompleteName: decodedCompleteName, item.Inscribable, item.Quantity, item.Modifiers, item.ItemType);
|
||||
}));
|
||||
return (tuple.Type, Items: decodedItems.ToList());
|
||||
}));
|
||||
decodedItems.Add((item.ModelId, item.EncodedName, decodedName, item.EncodedSingleName, decodedSingleName, item.EncodedCompleteName, decodedCompleteName, item.Inscribable, item.Quantity, item.Modifiers, item.ItemType));
|
||||
}
|
||||
decodedItemTuples.Add((tuple.Type, decodedItems));
|
||||
}
|
||||
|
||||
return new InventoryInformation(
|
||||
Bags: [.. decodedItemTuples.Select(tuple =>
|
||||
|
||||
@@ -8,12 +8,10 @@ namespace Daybreak.API.Services;
|
||||
|
||||
public sealed class UIService(
|
||||
GameThreadService gameThreadService,
|
||||
GameContextService gameContextService,
|
||||
UIContextService uIContextService,
|
||||
ILogger<UIService> logger)
|
||||
{
|
||||
private readonly GameThreadService gameThreadService = gameThreadService;
|
||||
private readonly GameContextService gameContextService = gameContextService;
|
||||
private readonly UIContextService uIContextService = uIContextService;
|
||||
private readonly ILogger<UIService> logger = logger;
|
||||
|
||||
@@ -90,62 +88,8 @@ public sealed class UIService(
|
||||
|
||||
public async Task<string?> DecodeString(string encoded, Language language, CancellationToken cancellationToken)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
var prevLanguage = await this.gameThreadService.QueueOnGameThread<Language?>(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var gameContext = this.gameContextService.GetGameContext();
|
||||
if (gameContext.IsNull)
|
||||
{
|
||||
scopedLogger.LogError("Game context is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
var textParser = gameContext.Pointer->TextParserContext;
|
||||
if (textParser is null)
|
||||
{
|
||||
scopedLogger.LogError("Text parser context is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
var prevLanguage = textParser->Language;
|
||||
if (language is not Language.Unknown)
|
||||
{
|
||||
textParser->Language = language;
|
||||
}
|
||||
|
||||
return prevLanguage;
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
var decoded = await this.uIContextService.AsyncDecodeStringAsync(encoded, cancellationToken);
|
||||
await this.gameThreadService.QueueOnGameThread(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var gameContext = this.gameContextService.GetGameContext();
|
||||
if (gameContext.IsNull)
|
||||
{
|
||||
scopedLogger.LogError("Game context is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var textParser = gameContext.Pointer->TextParserContext;
|
||||
if (textParser is null)
|
||||
{
|
||||
scopedLogger.LogError("Text parser context is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevLanguage.HasValue)
|
||||
{
|
||||
textParser->Language = prevLanguage.Value;
|
||||
}
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
return decoded;
|
||||
// GWCA's AsyncDecodeStr handles language switching internally (sets language, decodes, restores)
|
||||
return await this.uIContextService.AsyncDecodeStringAsync(encoded, language, cancellationToken);
|
||||
}
|
||||
|
||||
private static ControlAction GetUIActionFromFrameLabel(string frameLabel)
|
||||
|
||||
Reference in New Issue
Block a user