Improve toolbox integration (Closes #1532) (#1534)

This commit is contained in:
2026-05-13 13:30:50 +02:00
parent 10fb70787b
commit dd89e3316e
6 changed files with 80 additions and 21 deletions
+30 -2
View File
@@ -5,13 +5,41 @@
"name": "Debug Daybreak.Linux",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/Daybreak.Linux/Daybreak.Linux.csproj"
"projectPath": "${workspaceFolder}/Daybreak.Linux/Daybreak.Linux.csproj",
"console": "integratedTerminal",
"justMyCode": true,
"stopAtEntry": false,
"suppressJITOptimizations": false,
"symbolOptions": {
"searchMicrosoftSymbolServer": false,
"searchNuGetOrgSymbolServer": false
},
"logging": {
"engineLogging": false,
"moduleLoad": false,
"exceptions": false,
"browserStdOut": false
}
},
{
"name": "Debug Daybreak.Windows",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/Daybreak.Windows/Daybreak.Windows.csproj"
"projectPath": "${workspaceFolder}/Daybreak.Windows/Daybreak.Windows.csproj",
"console": "integratedTerminal",
"justMyCode": true,
"stopAtEntry": false,
"suppressJITOptimizations": false,
"symbolOptions": {
"searchMicrosoftSymbolServer": false,
"searchNuGetOrgSymbolServer": false
},
"logging": {
"engineLogging": false,
"moduleLoad": false,
"exceptions": false,
"browserStdOut": false
}
}
]
}
+26 -7
View File
@@ -182,12 +182,35 @@ public class EntryPoint
private static void PreloadNativeDependencies(ScopedLogger<EntryPoint> logger)
{
// First, check if gwca.dll is already loaded in the process (e.g. by GWToolbox).
// If so, reuse it via a ref-count bump on the existing path rather than loading
// a second copy from the Daybreak.API directory. Loading a second instance of
// gwca.dll would result in duplicate native modules in the process, which can
// lead to inconsistent state across mods that depend on it.
string? daybreakApiDir = default;
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (module.ModuleName?.Contains("Daybreak.API", StringComparison.OrdinalIgnoreCase) is true)
if (module.ModuleName?.Equals("gwca.dll", StringComparison.OrdinalIgnoreCase) is true)
{
var moduleDir = Path.GetDirectoryName(module.FileName)!;
var gwcaPath = Path.Combine(moduleDir, "gwca.dll");
NativeLibrary.Load(module.FileName);
logger.LogDebug($"Reused already-loaded gwca.dll from {module.FileName}");
return;
}
if (daybreakApiDir is null &&
module.ModuleName?.Contains("Daybreak.API", StringComparison.OrdinalIgnoreCase) is true)
{
daybreakApiDir = Path.GetDirectoryName(module.FileName);
}
}
if (daybreakApiDir is null)
{
logger.LogError("Could not locate Daybreak.API module directory to preload gwca.dll");
return;
}
var gwcaPath = Path.Combine(daybreakApiDir, "gwca.dll");
if (File.Exists(gwcaPath))
{
NativeLibrary.Load(gwcaPath);
@@ -197,9 +220,5 @@ public class EntryPoint
{
logger.LogError($"gwca.dll not found at {gwcaPath}");
}
break;
}
}
}
}
+9 -4
View File
@@ -13,7 +13,7 @@ namespace Daybreak.API.Interop
{
/// <summary>
/// P/Invoke bindings for 527 C++ exports from gwca.dll (0 skipped).
/// P/Invoke bindings for 528 C++ exports from gwca.dll (0 skipped).
/// Nested classes mirror the C++ namespace hierarchy (e.g. GW::Agents → GWCA.GW.Agents).
/// Types annotated with [GWCAEquivalent] are used in signatures where available.
/// </summary>
@@ -59,7 +59,7 @@ public static unsafe partial class GWCA
// [NAMESPACE] GWCA.GW.StoC popped at line 50
// [NAMESPACE] GWCA.GW.TargetFilter popped at line 112
// [NAMESPACE] GWCA.GW.Trade popped at line 22
// [NAMESPACE] GWCA.GW.UI popped at line 774
// [NAMESPACE] GWCA.GW.UI popped at line 31
// [NAMESPACE] GWCA.GW.UI.UIPacket popped at line 30
// [NAMESPACE] GWCA.GWCA popped at line 15
// GWCA.GW.AccountContext: 9 fields [OK]
@@ -1759,6 +1759,11 @@ public static unsafe partial class GWCA
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool SetHeroBehavior(uint value1, global::Daybreak.API.Interop.GWCA.GW.HeroBehavior heroBehavior2);
// GW::PartyMgr::SetHeroSkillDisabled
[LibraryImport(DllName, EntryPoint = "?SetHeroSkillDisabled@PartyMgr@GW@@YA_NII_N@Z")]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool SetHeroSkillDisabled(uint value1, uint value2, [MarshalAs(UnmanagedType.U1)] bool flag3);
// GW::PartyMgr::SetHeroTarget
[LibraryImport(DllName, EntryPoint = "?SetHeroTarget@PartyMgr@GW@@YA_NII@Z")]
[return: MarshalAs(UnmanagedType.U1)]
@@ -8121,7 +8126,7 @@ public static unsafe partial class GWCA
public static partial class Urgoz
{
internal const int HoppingVampire = 3796;
internal const int Urgoz_ = 3805;
internal const int Urgoz = 3805;
public static partial class Deep
{
@@ -8255,7 +8260,7 @@ public static unsafe partial class GWCA
internal const int LockedChest = 8192; // this is actually ->ExtraType
internal const int MiniatureLegionnaire = 8035;
public static partial class Minipet_
public static partial class Minipet
{
internal const int MiniatureConfessorDorian = 8344;
internal const int MiniatureConfessorIsaiah = 8352;
@@ -383,8 +383,11 @@ public class ProjectConfiguration : PluginConfigurationBase
public override void RegisterMods(IModsProducer modsManager)
{
modsManager.RegisterMod<IGuildWarsVersionChecker, GuildWarsVersionChecker>();
modsManager.RegisterMod<IDaybreakApiService, DaybreakApiService>();
// Toolbox must be registered before Daybreak API so that Toolbox injects first
// and loads its gwca.dll. Daybreak API will then reuse the already-loaded gwca.dll
// instead of loading a second copy, avoiding duplicate native module loads.
modsManager.RegisterMod<IToolboxService, ToolboxService>();
modsManager.RegisterMod<IDaybreakApiService, DaybreakApiService>();
modsManager.RegisterMod<IUModService, UModService>();
modsManager.RegisterMod<IDirectSongService, DirectSongService>(singleton: true);
}
@@ -366,6 +366,10 @@ internal sealed class ToolboxService(
if (await this.processInjector.Inject(process, dll, cancellationToken))
{
scopedLogger.LogDebug("Injected toolbox dll");
// Give Toolbox a moment to load its bundled gwca.dll into the target process
// before any subsequent mod (e.g. Daybreak.API) tries to load its own copy.
// This ensures Daybreak.API's existing-module check sees gwca.dll and reuses it.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
this.notificationService.NotifyInformation(
title: "GWToolbox started",
description: "GWToolbox has been injected");
BIN
View File
Binary file not shown.