diff --git a/Daybreak.API/Interop/GWCA.cs b/Daybreak.API/Interop/GWCA.cs index 0d095232..cdb4f281 100644 --- a/Daybreak.API/Interop/GWCA.cs +++ b/Daybreak.API/Interop/GWCA.cs @@ -13,7 +13,7 @@ namespace Daybreak.API.Interop { /// -/// P/Invoke bindings for 545 C++ exports from gwca.dll (0 skipped). +/// P/Invoke bindings for 560 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. /// @@ -23,11 +23,11 @@ public static unsafe partial class GWCA // ═══════════════════════════════════════════════════ // Parsed structs diagnostic: // [NAMESPACE] GWCA.GW popped at line 46 - // [NAMESPACE] GWCA.GW.Agents popped at line 215 - // [NAMESPACE] GWCA.GW.CameraMgr popped at line 35 - // [NAMESPACE] GWCA.GW.Chat popped at line 21 - // [NAMESPACE] GWCA.GW.Chat.TextColor popped at line 50 - // [NAMESPACE] GWCA.GW.Constants popped at line 25 + // [NAMESPACE] GWCA.GW.Agents popped at line 211 + // [NAMESPACE] GWCA.GW.CameraMgr popped at line 34 + // [NAMESPACE] GWCA.GW.Chat popped at line 13 + // [NAMESPACE] GWCA.GW.Chat.TextColor popped at line 54 + // [NAMESPACE] GWCA.GW.Constants popped at line 17 // [NAMESPACE] GWCA.GW.Constants.DialogID popped at line 282 // [NAMESPACE] GWCA.GW.Constants.ItemID popped at line 201 // [NAMESPACE] GWCA.GW.Constants.ModelID.Minipet.SummoningStone.FoW.UW.FoW.Urgoz.Deep.DoA.EotnDungeons.BonusMissionPack.EotnDungeons popped at line 445 @@ -35,31 +35,31 @@ public static unsafe partial class GWCA // [NAMESPACE] GWCA.GW.Constants.Preference popped at line 62 // [NAMESPACE] GWCA.GW.Constants.Range popped at line 239 // [NAMESPACE] GWCA.GW.Constants.SqrRange popped at line 249 - // [NAMESPACE] GWCA.GW.Effects popped at line 60 - // [NAMESPACE] GWCA.GW.EventMgr popped at line 32 - // [NAMESPACE] GWCA.GW.FriendListMgr popped at line 32 - // [NAMESPACE] GWCA.GW.GameThread popped at line 32 + // [NAMESPACE] GWCA.GW.Effects popped at line 58 + // [NAMESPACE] GWCA.GW.EventMgr popped at line 83 + // [NAMESPACE] GWCA.GW.FriendListMgr popped at line 35 + // [NAMESPACE] GWCA.GW.GameThread popped at line 31 // [NAMESPACE] GWCA.GW.GuildMgr popped at line 21 - // [NAMESPACE] GWCA.GW.Hook popped at line 22 + // [NAMESPACE] GWCA.GW.Hook popped at line 127 // [NAMESPACE] GWCA.GW.Items popped at line 97 - // [NAMESPACE] GWCA.GW.Map popped at line 165 - // [NAMESPACE] GWCA.GW.MemoryMgr popped at line 30 + // [NAMESPACE] GWCA.GW.Map popped at line 166 + // [NAMESPACE] GWCA.GW.MemoryMgr popped at line 28 // [NAMESPACE] GWCA.GW.Merchant popped at line 30 - // [NAMESPACE] GWCA.GW.Packet popped at line 112 - // [NAMESPACE] GWCA.GW.Packet.StoC popped at line 107 - // [NAMESPACE] GWCA.GW.Packet.StoC.GenericValueID popped at line 70 - // [NAMESPACE] GWCA.GW.Packet.StoC.JumboMessageType popped at line 82 - // [NAMESPACE] GWCA.GW.Packet.StoC.JumboMessageValue popped at line 99 - // [NAMESPACE] GWCA.GW.PartyMgr popped at line 108 + // [NAMESPACE] GWCA.GW.Packet popped at line 91 + // [NAMESPACE] GWCA.GW.Packet.StoC popped at line 86 + // [NAMESPACE] GWCA.GW.Packet.StoC.GenericValueID popped at line 58 + // [NAMESPACE] GWCA.GW.Packet.StoC.JumboMessageType popped at line 70 + // [NAMESPACE] GWCA.GW.Packet.StoC.JumboMessageValue popped at line 78 + // [NAMESPACE] GWCA.GW.PartyMgr popped at line 107 // [NAMESPACE] GWCA.GW.PlayerMgr popped at line 56 // [NAMESPACE] GWCA.GW.QuestMgr popped at line 43 - // [NAMESPACE] GWCA.GW.Render popped at line 127 - // [NAMESPACE] GWCA.GW.Scanner popped at line 52 + // [NAMESPACE] GWCA.GW.Render popped at line 167 + // [NAMESPACE] GWCA.GW.Scanner popped at line 119 // [NAMESPACE] GWCA.GW.SkillbarMgr popped at line 43 - // [NAMESPACE] GWCA.GW.StoC popped at line 50 - // [NAMESPACE] GWCA.GW.TargetFilter popped at line 122 + // [NAMESPACE] GWCA.GW.StoC popped at line 45 + // [NAMESPACE] GWCA.GW.TargetFilter popped at line 126 // [NAMESPACE] GWCA.GW.Trade popped at line 22 - // [NAMESPACE] GWCA.GW.UI popped at line 806 + // [NAMESPACE] GWCA.GW.UI popped at line 833 // [NAMESPACE] GWCA.GW.UI.UIPacket popped at line 30 // [NAMESPACE] GWCA.GWCA popped at line 15 // GWCA.GW.AccountContext: 9 fields [OK] @@ -116,6 +116,7 @@ public static unsafe partial class GWCA // GWCA.GW.HeroFlag: 8 fields [OK] // GWCA.GW.HeroInfo: 35 fields [OK] // GWCA.GW.HeroPartyMember: 6 fields [OK] + // GWCA.GW.Hook.HookTarget: 1 fields [OK] // GWCA.GW.Inventory: 39 fields [OK] // GWCA.GW.InventoryTableEntry: 3 fields [OK] // GWCA.GW.Item: 26 fields [OK] @@ -126,10 +127,13 @@ public static unsafe partial class GWCA // GWCA.GW.ItemModifier: 1 fields [OK] // GWCA.GW.LoginCharacter: 2 fields [OK] // GWCA.GW.MapAgent: 13 fields [OK] - // GWCA.GW.MapContext: 57 fields [OK] - // GWCA.GW.MapProp: 18 fields [OK] + // GWCA.GW.MapContext: 51 fields [OK] + // GWCA.GW.MapLightContext: 2 fields [OK] + // GWCA.GW.MapLightDescriptor: 8 fields [OK] + // GWCA.GW.MapProp: 20 fields [OK] // GWCA.GW.MapStaticData: 37 fields [SKIP: unresolved typedef in field map: PathingMapArray] // GWCA.GW.MapTypeInstanceInfo: 3 fields [OK] + // GWCA.GW.MapWaterContext: 8 fields [OK] // GWCA.GW.Mat4x3f: 13 fields [OK] // GWCA.GW.MaterialCost: 4 fields [OK] // GWCA.GW.Merchant.QuoteInfo: 2 fields [OK] @@ -175,13 +179,14 @@ public static unsafe partial class GWCA // GWCA.GW.ProfessionState: 5 fields [OK] // GWCA.GW.ProgressBarContext: 5 fields [OK] // GWCA.GW.PropByType: 2 fields [OK] - // GWCA.GW.PropModelInfo: 6 fields [OK] + // GWCA.GW.PropModelInfo: 5 fields [OK] // GWCA.GW.PropsContext: 6 fields [SKIP: complex template in field propsByType: Array>] // GWCA.GW.PvPItemInfo: 1 fields [OK] // GWCA.GW.PvPItemUpgradeInfo: 10 fields [OK] // GWCA.GW.Quest: 11 fields [OK] // GWCA.GW.RecObject: 9 fields [OK] - // GWCA.GW.Render.Mat4x3f: 13 fields [OK] + // GWCA.GW.Render.GlesDevice: 12 fields [OK] + // GWCA.GW.Render.GlFragmentRenderState: 5 fields [OK] // GWCA.GW.SalvageSessionInfo: 9 fields [OK] // GWCA.GW.ScannerSectionOffset: 2 fields [OK] // GWCA.GW.ScrollableFrame: 0 fields [SKIP: no fields] @@ -196,6 +201,10 @@ public static unsafe partial class GWCA // GWCA.GW.SubStructUnk: 9 fields [OK] // GWCA.GW.TabsFrame: 0 fields [SKIP: no fields] // GWCA.GW.TagInfo: 4 fields [OK] + // GWCA.GW.TerrainShadowRecord: 3 fields [OK] + // GWCA.GW.TerrainTexContext: 5 fields [SKIP: complex template in field shadow_records: BaseArray] + // GWCA.GW.TerrainTexTile: 5 fields [OK] + // GWCA.GW.TerrainTexture: 4 fields [OK] // GWCA.GW.TextCache: 1 fields [OK] // GWCA.GW.TextParser: 14 fields [OK] // GWCA.GW.THash: 5 fields [SKIP: template param T in field m_fullList] @@ -209,7 +218,7 @@ public static unsafe partial class GWCA // GWCA.GW.TradeContext: 4 fields [OK] // GWCA.GW.TradeItem: 2 fields [OK] // GWCA.GW.TradePlayer: 2 fields [OK] - // GWCA.GW.UI.AgentNameTagInfo: 15 fields [OK] + // GWCA.GW.UI.AgentNameTagInfo: 17 fields [OK] // GWCA.GW.UI.ChatTemplate: 4 fields [OK] // GWCA.GW.UI.CompassPoint: 2 fields [OK] // GWCA.GW.UI.CreateUIComponentPacket: 6 fields [OK] @@ -271,7 +280,6 @@ public static unsafe partial class GWCA // GWCA.GW.UI.UIPacket.kSendMerchantTransactItem: 5 fields [OK] // GWCA.GW.UI.UIPacket.kSendMoveItem: 4 fields [OK] // GWCA.GW.UI.UIPacket.kSendPingWeaponSet: 3 fields [OK] - // GWCA.GW.UI.UIPacket.kSendUseItem: 2 fields [OK] // GWCA.GW.UI.UIPacket.kSendWorldAction: 3 fields [OK] // GWCA.GW.UI.UIPacket.kServerActiveQuestChanged: 5 fields [OK] // GWCA.GW.UI.UIPacket.kSetAgentProfession: 3 fields [OK] @@ -621,10 +629,11 @@ public static partial class Agents [return: MarshalAs(UnmanagedType.U1)] public static partial bool Move(global::Daybreak.API.Interop.GuildWars.GamePos gamePos); - // GW::Agents::RegisterAgentEffectCallback | function2: TODO: map struct function - // [LibraryImport(DllName, EntryPoint = "?RegisterAgentEffectCallback@Agents@GW@@YAXPAUHookEntry@2@ABV?$function@$$A6AXPAUHookStatus@GW@@PBUAgentLiving@2@PBUAgentEffect@2@@Z@std@@H@Z")] - // [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - // public static partial void RegisterAgentEffectCallback(global::Daybreak.API.Interop.HookEntry* hookEntry1, function* function2, global::Daybreak.API.Interop.GuildWars.AgentLiving* agentLiving3, global::Daybreak.API.Interop.GuildWars.AgentEffect* agentEffect4); + // GW::Agents::RefreshAgentNameTag + [LibraryImport(DllName, EntryPoint = "?RefreshAgentNameTag@Agents@GW@@YA_NPBUAgent@2@@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool RefreshAgentNameTag(global::Daybreak.API.Interop.GuildWars.Agent* agent); // GW::Agents::SendDialog [LibraryImport(DllName, EntryPoint = "?SendDialog@Agents@GW@@YA_NI@Z")] @@ -1058,6 +1067,12 @@ public static partial class EventMgr [LibraryImport(DllName, EntryPoint = "?RemoveEventCallback@EventMgr@GW@@YAXPAUHookEntry@2@W4EventID@12@@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void RemoveEventCallback(global::Daybreak.API.Interop.HookEntry* hookEntry1, global::Daybreak.API.Interop.GWCA.GW.EventMgr.EventID eventID2); + + // GW::EventMgr::SendEventMessage + [LibraryImport(DllName, EntryPoint = "?SendEventMessage@EventMgr@GW@@YA_NW4EventID@12@PAXI@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool SendEventMessage(global::Daybreak.API.Interop.GWCA.GW.EventMgr.EventID eventID1, void* ptr2, uint value3); } public static partial class FriendListMgr @@ -1245,9 +1260,9 @@ public static partial class Hook { // GW::Hook::CreateHook - [LibraryImport(DllName, EntryPoint = "?CreateHook@Hook@GW@@YAHPAPAXPAX0@Z")] + [LibraryImport(DllName, EntryPoint = "?CreateHook@Hook@GW@@YAHPAPAXUHookTarget@12@0@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial int CreateHook(void* ptr1, void* ptr2, nint ptr3); + public static partial int CreateHook(void* ptr1, global::Daybreak.API.Interop.GuildWars.HookTarget hookTarget2, nint ptr3); // GW::Hook::Deinitialize [LibraryImport(DllName, EntryPoint = "?Deinitialize@Hook@GW@@YAXXZ")] @@ -1255,14 +1270,14 @@ public static partial class Hook public static partial void Deinitialize(); // GW::Hook::DisableHooks - [LibraryImport(DllName, EntryPoint = "?DisableHooks@Hook@GW@@YAXPAX@Z")] + [LibraryImport(DllName, EntryPoint = "?DisableHooks@Hook@GW@@YAXUHookTarget@12@@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial void DisableHooks(void* ptr); + public static partial void DisableHooks(global::Daybreak.API.Interop.GuildWars.HookTarget hookTarget); // GW::Hook::EnableHooks - [LibraryImport(DllName, EntryPoint = "?EnableHooks@Hook@GW@@YAXPAX@Z")] + [LibraryImport(DllName, EntryPoint = "?EnableHooks@Hook@GW@@YAXUHookTarget@12@@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial void EnableHooks(void* ptr); + public static partial void EnableHooks(global::Daybreak.API.Interop.GuildWars.HookTarget hookTarget); // GW::Hook::EnterHook [LibraryImport(DllName, EntryPoint = "?EnterHook@Hook@GW@@YAXXZ")] @@ -1285,9 +1300,9 @@ public static partial class Hook public static partial void LeaveHook(); // GW::Hook::RemoveHook - [LibraryImport(DllName, EntryPoint = "?RemoveHook@Hook@GW@@YAXPAX@Z")] + [LibraryImport(DllName, EntryPoint = "?RemoveHook@Hook@GW@@YAXUHookTarget@12@@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial void RemoveHook(void* ptr); + public static partial void RemoveHook(global::Daybreak.API.Interop.GuildWars.HookTarget hookTarget); } public static partial class Item @@ -1640,6 +1655,11 @@ public static partial class Map [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial int GetDistrict(); + // GW::Map::GetDistrictCount + [LibraryImport(DllName, EntryPoint = "?GetDistrictCount@Map@GW@@YAIXZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial uint GetDistrictCount(); + // GW::Map::GetFoesKilled [LibraryImport(DllName, EntryPoint = "?GetFoesKilled@Map@GW@@YAIXZ")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -1872,6 +1892,25 @@ public static partial class MultiLineTextLabelFrame public static partial double SetLabel(nint self, ushort* ptr2); } + public static partial class NPCEquipment + { + + // GW::NPCEquipment::GetType + [LibraryImport(DllName, EntryPoint = "?GetType@NPCEquipment@GW@@QAEIXZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvThiscall)])] + public static partial void GetType(nint self); + + // GW::NPCEquipment::RedrawEquipmentSlot + [LibraryImport(DllName, EntryPoint = "?RedrawEquipmentSlot@NPCEquipment@GW@@QAE_NI@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvThiscall)])] + public static partial double RedrawEquipmentSlot(nint self, uint value2); + + // GW::NPCEquipment::UndrawEquipmentSlot + [LibraryImport(DllName, EntryPoint = "?UndrawEquipmentSlot@NPCEquipment@GW@@QAE_NI@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvThiscall)])] + public static partial double UndrawEquipmentSlot(nint self, uint value2); + } + public static partial class PartyMgr { @@ -2312,6 +2351,11 @@ public static partial class Render [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void FlushCommandQueue(); + // GW::Render::GetBackend + [LibraryImport(DllName, EntryPoint = "?GetBackend@Render@GW@@YA?AW4Backend@12@XZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial global::Daybreak.API.Interop.GWCA.GW.Render.Backend GetBackend(); + // GW::Render::GetDevice | returns TODO: map struct IDirect3DDevice9 // [LibraryImport(DllName, EntryPoint = "?GetDevice@Render@GW@@YAPAUIDirect3DDevice9@@XZ")] // [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -2322,11 +2366,31 @@ public static partial class Render [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial float GetFieldOfView(); + // GW::Render::GetFrameCallback + [LibraryImport(DllName, EntryPoint = "?GetFrameCallback@Render@GW@@YAP6AXXZXZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint GetFrameCallback(); + // GW::Render::GetFrameLimit [LibraryImport(DllName, EntryPoint = "?GetFrameLimit@Render@GW@@YAIXZ")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial uint GetFrameLimit(); + // GW::Render::GetGlesDevice + [LibraryImport(DllName, EntryPoint = "?GetGlesDevice@Render@GW@@YAPAUGlesDevice@12@XZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial global::Daybreak.API.Interop.GuildWars.GlesDevice* GetGlesDevice(); + + // GW::Render::GetGlesRenderCallback + [LibraryImport(DllName, EntryPoint = "?GetGlesRenderCallback@Render@GW@@YAP6AXPAUGlesDevice@12@@ZXZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint GetGlesRenderCallback(); + + // GW::Render::GetGlesResetCallback + [LibraryImport(DllName, EntryPoint = "?GetGlesResetCallback@Render@GW@@YAP6AXPAUGlesDevice@12@@ZXZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint GetGlesResetCallback(); + // GW::Render::GetGraphicsRendererValue [LibraryImport(DllName, EntryPoint = "?GetGraphicsRendererValue@Render@GW@@YAIW4Metric@12@I@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -2337,21 +2401,20 @@ public static partial class Render [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial int GetIsFullscreen(); - // GW::Render::GetIsInRenderLoop - [LibraryImport(DllName, EntryPoint = "?GetIsInRenderLoop@Render@GW@@YA_NXZ")] - [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - [return: MarshalAs(UnmanagedType.U1)] - public static partial bool GetIsInRenderLoop(); - // GW::Render::GetRenderCallback [LibraryImport(DllName, EntryPoint = "?GetRenderCallback@Render@GW@@YAP6AXPAUIDirect3DDevice9@@@ZXZ")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial nint GetRenderCallback(); - // GW::Render::GetTransform - [LibraryImport(DllName, EntryPoint = "?GetTransform@Render@GW@@YAPAUMat4x3f@12@W4Transform@12@@Z")] + // GW::Render::GetRendererMode + [LibraryImport(DllName, EntryPoint = "?GetRendererMode@Render@GW@@YA?AW4RendererMode@12@XZ")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial global::Daybreak.API.Interop.GWCA.GW.Render.RendererMode GetRendererMode(); + + // GW::Render::GetResetCallback + [LibraryImport(DllName, EntryPoint = "?GetResetCallback@Render@GW@@YAP6AXPAUIDirect3DDevice9@@@ZXZ")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial global::Daybreak.API.Interop.GuildWars.Mat4x3f* GetTransform(global::Daybreak.API.Interop.GWCA.GW.Render.Transform transform); + public static partial nint GetResetCallback(); // GW::Render::GetViewportHeight [LibraryImport(DllName, EntryPoint = "?GetViewportHeight@Render@GW@@YAIXZ")] @@ -2374,12 +2437,27 @@ public static partial class Render [return: MarshalAs(UnmanagedType.U1)] public static partial bool SetFog([MarshalAs(UnmanagedType.U1)] bool flag); + // GW::Render::SetFrameCallback + [LibraryImport(DllName, EntryPoint = "?SetFrameCallback@Render@GW@@YAXP6AXXZ@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial void SetFrameCallback(nint callback); + // GW::Render::SetFrameLimit [LibraryImport(DllName, EntryPoint = "?SetFrameLimit@Render@GW@@YA_NI@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] [return: MarshalAs(UnmanagedType.U1)] public static partial bool SetFrameLimit(uint value); + // GW::Render::SetGlesRenderCallback + [LibraryImport(DllName, EntryPoint = "?SetGlesRenderCallback@Render@GW@@YAXP6AXPAUGlesDevice@12@@Z@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial void SetGlesRenderCallback(nint callback); + + // GW::Render::SetGlesResetCallback + [LibraryImport(DllName, EntryPoint = "?SetGlesResetCallback@Render@GW@@YAXP6AXPAUGlesDevice@12@@Z@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial void SetGlesResetCallback(nint callback); + // GW::Render::SetGraphicsRendererValue [LibraryImport(DllName, EntryPoint = "?SetGraphicsRendererValue@Render@GW@@YA_NW4Metric@12@II@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -2415,6 +2493,11 @@ public static partial class Scanner [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial uint FindInRange(byte* ptr1, nint ptr2, int value3, uint value4, uint value5); + // GW::Scanner::FindNth + [LibraryImport(DllName, EntryPoint = "?FindNth@Scanner@GW@@YAIPBD0IHW4ScannerSection@2@@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial uint FindNth(byte* ptr1, nint ptr2, uint value3, int value4, global::Daybreak.API.Interop.GWCA.GW.ScannerSection scannerSection5); + // GW::Scanner::FindNthUseOfString [LibraryImport(DllName, EntryPoint = "?FindNthUseOfString@Scanner@GW@@YAIPBDIHW4ScannerSection@2@@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -2476,9 +2559,9 @@ public static partial class ScrollableFrame { // GW::ScrollableFrame::AddItem - [LibraryImport(DllName, EntryPoint = "?AddItem@ScrollableFrame@GW@@QAE_NIIP6AXPAUInteractionMessage@UI@2@PAX1@Z@Z")] + [LibraryImport(DllName, EntryPoint = "?AddItem@ScrollableFrame@GW@@QAEIIIP6AXPAUInteractionMessage@UI@2@PAX1@Z@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvThiscall)])] - public static partial double AddItem(nint self, uint value2, uint value3, nint callback4); + public static partial uint AddItem(nint self, uint value2, nint callback3); // GW::ScrollableFrame::ClearItems [LibraryImport(DllName, EntryPoint = "?ClearItems@ScrollableFrame@GW@@QAE_NXZ")] @@ -2603,10 +2686,10 @@ public static partial class SkillbarMgr [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial global::Daybreak.API.Interop.GuildWars.Skill* GetSkillConstantData(global::Daybreak.API.Interop.GWCA.GW.Constants.SkillID skillID); - // GW::SkillbarMgr::GetSkillConstantDataCount - [LibraryImport(DllName, EntryPoint = "?GetSkillConstantDataCount@SkillbarMgr@GW@@YAIXZ")] + // GW::SkillbarMgr::GetSkillCount + [LibraryImport(DllName, EntryPoint = "?GetSkillCount@SkillbarMgr@GW@@YAIXZ")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial uint GetSkillConstantDataCount(); + public static partial uint GetSkillCount(); // GW::SkillbarMgr::GetSkillSlot [LibraryImport(DllName, EntryPoint = "?GetSkillSlot@SkillbarMgr@GW@@YAHW4SkillID@Constants@2@@Z")] @@ -2914,6 +2997,12 @@ public static partial class Trade public static partial class UI { + // GW::UI::AddFrameCallback + [LibraryImport(DllName, EntryPoint = "?AddFrameCallback@UI@GW@@YA_NPAUFrame@12@P6AXPAUInteractionMessage@12@PAX2@Z2@Z")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool AddFrameCallback(global::Daybreak.API.Interop.Frame* frame1, nint callback2, nint ptr3); + // GW::UI::AddFrameUIInteractionCallback [LibraryImport(DllName, EntryPoint = "?AddFrameUIInteractionCallback@UI@GW@@YA_NPAUFrame@12@P6AXPAUInteractionMessage@12@PAX2@Z2@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] @@ -3213,9 +3302,9 @@ public static partial class UI public static partial bool SetCommandLinePref(ushort* ptr1, ushort* ptr2); // GW::UI::SetFrameBounds - [LibraryImport(DllName, EntryPoint = "?SetFrameBounds@UI@GW@@YAXPAUFrame@12@IPAM1@Z")] + [LibraryImport(DllName, EntryPoint = "?SetFrameBounds@UI@GW@@YAXPAUFrame@12@W4FrameLayoutMode@12@PAM2@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial void SetFrameBounds(global::Daybreak.API.Interop.Frame* frame1, uint value2, float* ptr3, nint ptr4); + public static partial void SetFrameBounds(global::Daybreak.API.Interop.Frame* frame1, global::Daybreak.API.Interop.GWCA.GW.UI.FrameLayoutMode frameLayoutMode2, float* ptr3, nint ptr4); // GW::UI::SetFrameDisabled [LibraryImport(DllName, EntryPoint = "?SetFrameDisabled@UI@GW@@YA_NPAUFrame@12@_N@Z")] @@ -3230,9 +3319,9 @@ public static partial class UI public static partial bool SetFrameMargins(global::Daybreak.API.Interop.Frame* frame1, uint value2, float* ptr3, nint ptr4, uint value5); // GW::UI::SetFramePosition - [LibraryImport(DllName, EntryPoint = "?SetFramePosition@UI@GW@@YAXPAUFrame@12@IPAM@Z")] + [LibraryImport(DllName, EntryPoint = "?SetFramePosition@UI@GW@@YAXPAUFrame@12@W4FrameLayoutMode@12@PAM@Z")] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] - public static partial void SetFramePosition(global::Daybreak.API.Interop.Frame* frame1, uint value2, float* ptr3); + public static partial void SetFramePosition(global::Daybreak.API.Interop.Frame* frame1, global::Daybreak.API.Interop.GWCA.GW.UI.FrameLayoutMode frameLayoutMode2, float* ptr3); // GW::UI::SetFrameTitle [LibraryImport(DllName, EntryPoint = "?SetFrameTitle@UI@GW@@YA_NPAUFrame@12@PB_W@Z")] @@ -3474,6 +3563,20 @@ public enum HeroBehavior : uint AvoidCombat, } + public enum NameTagFlags : uint + { + NameTagFlags_Picked = 0x8, + NameTagFlags_Highlighted = 0x10, + NameTagFlags_InRange = 0x20, + NameTagFlags_EvaluatedTarget = 0x80, + NameTagFlags_ManualTarget = 0x100, + NameTagFlags_Suppressed = 0x200, + NameTagFlags_PassesFilter = 0x400, + NameTagFlags_NotOwnedByPlayer = 0x800, + NameTagFlags_PassesTransientFilter = 0x1000, + NameTagFlags_Disabled = 0x20000, + } + public enum ObserverMatchType : uint { SpecialEvent, @@ -8182,7 +8285,6 @@ public enum SkillID : uint Heroic_Refrain, Reforged_Mode = 0xD6A, Dhuums_Covenant_Broken, - Count = 0xD6c, } public enum SkillType : int @@ -8811,26 +8913,26 @@ public static partial class Minipet_ public static partial class SummoningStone { - internal const int ImperialBarrage = 9045; - internal const int ImperialCripplingSlash = 9043; - internal const int ImperialQuiveringBlade = 9046; - internal const int ImperialTripleChop = 9044; - internal const int TenguAngchuElementalist = 9051; - internal const int TenguAuraOftheLich = 9061; - internal const int TenguBroadHeadArrow = 9048; - internal const int TenguDefiantWasXinrae = 9062; - internal const int TenguFeveredDreams = 9052; - internal const int TenguGlassArrows = 9056; - internal const int TenguHundredBlades = 9047; - internal const int TenguLifeSheath = 9050; - internal const int TenguPalmStrike = 9049; - internal const int TenguPanic = 9060; - internal const int TenguPeaceandHarmony = 9058; - internal const int TenguPreservation = 9054; - internal const int TenguPrimalRage = 9055; - internal const int TenguSandstorm = 9059; - internal const int TenguSpitefulSpirit = 9053; - internal const int TenguWayOftheAssassin = 9057; + internal const int ImperialBarrage = 9270; + internal const int ImperialCripplingSlash = 9268; + internal const int ImperialQuiveringBlade = 9271; + internal const int ImperialTripleChop = 9269; + internal const int TenguAngchuElementalist = 9276; + internal const int TenguAuraOftheLich = 9286; + internal const int TenguBroadHeadArrow = 9273; + internal const int TenguDefiantWasXinrae = 9287; + internal const int TenguFeveredDreams = 9277; + internal const int TenguGlassArrows = 9281; + internal const int TenguHundredBlades = 9272; + internal const int TenguLifeSheath = 9275; + internal const int TenguPalmStrike = 9274; + internal const int TenguPanic = 9285; + internal const int TenguPeaceandHarmony = 9283; + internal const int TenguPreservation = 9279; + internal const int TenguPrimalRage = 9280; + internal const int TenguSandstorm = 9284; + internal const int TenguSpitefulSpirit = 9278; + internal const int TenguWayOftheAssassin = 9282; } } } @@ -8885,11 +8987,50 @@ public static partial class EventMgr public enum EventID : int { - kRecvPing = 0x8, - kSendFriendState = 0x26, - kRecvFriendState = 0x2c, + kAppActivated = 0x0, // WM_ACTIVATEAPP; packet is one dword, non-zero when activated + kFrameTick = 0x1, // once per frame from the pump, packet is the elapsed milliseconds + kRenderFinished = 0x2, // once per frame after the render, packet is one dword + kCharTyped = 0x3, // WM_CHAR: {character, repeat, modifier keys} + kCursorVisibility = 0x5, // packet is one dword, non-zero to show the cursor + kShutdown = 0x6, // posting this latches the event queue closed, so nothing dispatches after it + kScreenSizeChanged = 0x7, // {new width, new height, old width, old height}, raised inside the queue drain + kDriverError = 0x8, // the game's own handler logs "Driver internal error encountered." and quits + kRecvPing = kDriverError, // legacy GWCA name for 0x8; the game does not use this id for pings + kDpiChanged = 0xa, // WM_DPICHANGED: {x dpi, y dpi} + kGamepadAttached = 0xb, // the four below come from the same controller poll in EvtOs.cpp + kGamepadState = 0xc, // one dword + kGamepadAxis = 0xd, // four dwords + kGamepadButtonDown = 0xe, // {button, state} + kGamepadButtonUp = 0xf, // {button, state} + kCharTypedInjected = 0x13, // same shape as kCharTyped, from the game's own WM_APP+3 injection + kKeyDown = 0x1a, // {key, repeat, modifier keys}; the engine records the key as held here + kKeyUp = 0x1b, // same shape; the engine clears the held bit here + kFocusLost = 0x1c, // WM_KILLFOCUS, no packet + kMouseMoveRelative = 0x1d, // {button, 0, x, y, button mask, modifier keys} while the mouse is captured + kMouseButtonDown = 0x1e, // same shape as kMouseMoveRelative + kMouseLeave = 0x1f, // WM_MOUSELEAVE, no packet + kMouseMove = 0x20, // same shape as kMouseMoveRelative, only sent when the position changed + kMouseButtonUp = 0x21, // same shape; also synthesised for every held button when capture is lost + kMouseWheel = 0x22, // same shape, with the wheel delta in place of the button + kWindowMoved = 0x23, // WM_MOVE: {x, y} + kFriend = 0x24, + kService = 0x25, // same packet shape as kFriend; FrApi forwards it as UI message 0x50 + kServiceAlt = 0x26, // same again, forwarded as UI message 0x51 + kFocusGained = 0x27, // WM_SETFOCUS, no packet + kWindowResized = 0x28, // WM_SIZE: {width, height} + kTouchDown = 0x2c, // the three touch ids carry {index, 0, x, y, touch mask} + kTouchMove = 0x2d, + kTouchUp = 0x2e, // also synthesised for every held touch when the sequence is cancelled + kReserved = 0x2a, kNone = 0xffff, } + + public enum FriendEventType : int + { + kStatusChanged = 0x26, + kLocationChanged = 0x28, + kFriendAddedOrRemoved = 0x2c, + } } public static partial class Merchant @@ -8920,6 +9061,13 @@ public enum TransactionType : uint public static partial class Render { + public enum Backend : uint + { + Unknown = 0, + D3D9, + GLES3, + } + public enum Metric : uint { Metric0, @@ -8951,11 +9099,11 @@ public enum Metric : uint Count, } - public enum Transform : int + public enum RendererMode : uint { - TRANSFORM_PROJECTION_MATRIX = 0, - TRANSFORM_MODEL_MATRIX = 1, - TRANSFORM_COUNT = 5, + Windowed = 0, + WindowedBorderless = 1, + WindowedFullscreen = 2, } } @@ -9258,6 +9406,20 @@ public enum FlagPreference : uint Count, } + public enum FrameLayoutMode : uint + { + FrameLayoutMode_None = 0, + FrameLayoutMode_AnchorBottom = 0x1, // vertical: anchor to the rect's bottom edge + FrameLayoutMode_Center = 0x2, // required alongside centered placement on either axis (asserts otherwise) + FrameLayoutMode_AnchorLeft = 0x4, // horizontal: anchor to the rect's left edge + FrameLayoutMode_AnchorRight = 0x8, // horizontal: anchor to the rect's right edge + FrameLayoutMode_AnchorTop = 0x10, // vertical: anchor to the rect's top edge + FrameLayoutMode_StretchWidth = 0x20, // horizontal: fill the rect's full width (also behaves as a left anchor) + FrameLayoutMode_StretchHeight = 0x40, // vertical: fill the rect's full height (also behaves as a bottom anchor) + FrameLayoutMode_AnchorHorizontalMargin = 0x80, // gate: trim the resolved rect inward on the AnchorLeft/AnchorRight edge(s) + FrameLayoutMode_AnchorVerticalMargin = 0x100, // gate: trim the resolved rect inward on the AnchorTop/AnchorBottom edge(s) + } + public enum NumberCommandLineParameter : uint { Unk1, @@ -9857,47 +10019,48 @@ public enum UIMessage : uint kInventoryRelated2, // 0x100001aa, added to GW 2026-02-26 kInventoryRelated3, // 0x100001ab, added to GW 2026-02-26 kInventoryRelated4, // 0x100001ac, added to GW 2026-04-28 - kEquipItem, // 0x100001ad, wparam = { item_id, agent_id } - kMoveItem, // 0x100001ae, wparam = { item_id, to_bag, to_slot, bool prompt } - kItemRelated_1, // 0x100001af - kItemTooltip, // 0x100001b0 - kItemRelated_3, // 0x100001b1, added to GW 2026-02-26 - kItemRelated_4, // 0x100001b2, added to GW 2026-02-26 - kItemRelated_5, // 0x100001b3, added to GW 2026-04-28 - kInitiateTrade, // 0x100001b4 - kMessage_0x100001a7, // 0x100001b5 - kMessage_0x100001a8, // 0x100001b6 - kMessage_0x100001a9, // 0x100001b7 - kMessage_0x100001aa, // 0x100001b8 - kPartySearchWindowDestroyed, // 0x100001b9 - kMessage_0x100001ac, // 0x100001ba - kPartySearchWindowCreated, // 0x100001bb - kMessage_0x100001ae, // 0x100001bc - kMessage_0x100001af, // 0x100001bd - kMessage_0x100001b0, // 0x100001be - kMessage_0x100001b1, // 0x100001bf - kMessage_0x100001b2, // 0x100001c0 - kMessage_0x100001b3, // 0x100001c1 - kMessage_0x100001b4, // 0x100001c2 - kMessage_0x100001b5, // 0x100001c3 - kInventoryAgentChanged, // 0x100001c4, Triggered when inventory needs updating due to agent change; no args - kInventoryRelated_1, // 0x100001c5 - kInventoryRelated_2, // 0x100001c6 - kMissionStatusRelated, // 0x100001c7 - kUnused_1c2, // 0x100001c8 - kCollapseExpandSkillListSection, // 0x100001c9 - kPromptLoadTemplate, // 0x100001ca - kOpenTemplateManager, // 0x100001cb - kPromptSaveTemplate, // 0x100001cc - kOpenTemplate, // 0x100001cd, wparam = GW::UI::ChatTemplate* - kTemplateRelated_3, // 0x100001ce - kTemplateRelated_4, // 0x100001cf + kInventoryRelated4_1, // 0x100001ad, added to GW 2026-08-07 + kEquipItem, // 0x100001ae, wparam = { item_id, agent_id } + kMoveItem, // 0x100001af, wparam = { item_id, to_bag, to_slot, bool prompt } + kItemRelated_1, // 0x100001b0 + kItemTooltip, // 0x100001b1 + kItemRelated_3, // 0x100001b2, added to GW 2026-02-26 + kItemRelated_4, // 0x100001b3, added to GW 2026-02-26 + kItemRelated_5, // 0x100001b4, added to GW 2026-04-28 + kInitiateTrade, // 0x100001b5 + kMessage_0x100001a7, // 0x100001b6 + kMessage_0x100001a8, // 0x100001b7 + kMessage_0x100001a9, // 0x100001b8 + kMessage_0x100001aa, // 0x100001b9 + kPartySearchWindowDestroyed, // 0x100001ba + kMessage_0x100001ac, // 0x100001bb + kPartySearchWindowCreated, // 0x100001bc + kMessage_0x100001ae, // 0x100001bd + kMessage_0x100001af, // 0x100001be + kMessage_0x100001b0, // 0x100001bf + kMessage_0x100001b1, // 0x100001c0 + kMessage_0x100001b2, // 0x100001c1 + kMessage_0x100001b3, // 0x100001c2 + kMessage_0x100001b4, // 0x100001c3 + kMessage_0x100001b5, // 0x100001c4 + kInventoryAgentChanged, // 0x100001c5, Triggered when inventory needs updating due to agent change; no args + kInventoryRelated_1, // 0x100001c6 + kInventoryRelated_2, // 0x100001c7 + kMissionStatusRelated, // 0x100001c8 + kUnused_1c2, // 0x100001c9 + kCollapseExpandSkillListSection, // 0x100001ca + kPromptLoadTemplate, // 0x100001cb + kOpenTemplateManager, // 0x100001cc + kPromptSaveTemplate, // 0x100001cd + kOpenTemplate, // 0x100001ce, wparam = GW::UI::ChatTemplate* + kTemplateRelated_3, // 0x100001cf + kTemplateRelated_4, // 0x100001d0 kSendLoadSkillTemplate = 0x30000000 | 0x3, // 0x30000003, wparam = SkillbarMgr::SkillTemplate* kSendPingWeaponSet = 0x30000000 | 0x4, // 0x30000004, wparam = UIPacket::kSendPingWeaponSet* kSendMoveItem = 0x30000000 | 0x5, // 0x30000005, wparam = UIPacket::kSendMoveItem* kSendMerchantRequestQuote = 0x30000000 | 0x6, // 0x30000006, wparam = UIPacket::kSendMerchantRequestQuote* kSendMerchantTransactItem = 0x30000000 | 0x7, // 0x30000007, wparam = UIPacket::kSendMerchantTransactItem* - kSendUseItem = 0x30000000 | 0x8, // 0x30000008, wparam = UIPacket::kSendUseItem* + kSendUseItem = 0x30000000 | 0x8, // 0x30000008, wparam = uint32_t item_id kSendSetActiveQuest = 0x30000000 | 0x9, // 0x30000009, wparam = uint32_t quest_id kSendAbandonQuest = 0x30000000 | 0xA, // 0x3000000a, wparam = uint32_t quest_id kSendChangeTarget = 0x30000000 | 0xB, // 0x3000000b, wparam = UIPacket::kSendChangeTarget* // e.g. tell the gw client to focus on a different target @@ -10227,6 +10390,20 @@ public enum HeroBehavior : uint AvoidCombat, } + public enum NameTagFlags : uint + { + NameTagFlags_Picked = 0x8, + NameTagFlags_Highlighted = 0x10, + NameTagFlags_InRange = 0x20, + NameTagFlags_EvaluatedTarget = 0x80, + NameTagFlags_ManualTarget = 0x100, + NameTagFlags_Suppressed = 0x200, + NameTagFlags_PassesFilter = 0x400, + NameTagFlags_NotOwnedByPlayer = 0x800, + NameTagFlags_PassesTransientFilter = 0x1000, + NameTagFlags_Disabled = 0x20000, + } + public enum ObserverMatchType : uint { SpecialEvent, @@ -14885,7 +15062,6 @@ public enum SkillID : uint Heroic_Refrain, Reforged_Mode = 0xD6A, Dhuums_Covenant_Broken, - Count = 0xD6c, } public enum SkillType : int @@ -15345,6 +15521,20 @@ public enum FlagPreference : uint Count, } + public enum FrameLayoutMode : uint + { + FrameLayoutMode_None = 0, + FrameLayoutMode_AnchorBottom = 0x1, + FrameLayoutMode_Center = 0x2, + FrameLayoutMode_AnchorLeft = 0x4, + FrameLayoutMode_AnchorRight = 0x8, + FrameLayoutMode_AnchorTop = 0x10, + FrameLayoutMode_StretchWidth = 0x20, + FrameLayoutMode_StretchHeight = 0x40, + FrameLayoutMode_AnchorHorizontalMargin = 0x80, + FrameLayoutMode_AnchorVerticalMargin = 0x100, + } + public enum NumberCommandLineParameter : uint { Unk1, @@ -15944,6 +16134,7 @@ public enum UIMessage : uint kInventoryRelated2, kInventoryRelated3, kInventoryRelated4, + kInventoryRelated4_1, kEquipItem, kMoveItem, kItemRelated_1, @@ -16078,12 +16269,58 @@ public enum ActionState : uint public enum EventID : int { - kRecvPing = 0x8, - kSendFriendState = 0x26, - kRecvFriendState = 0x2c, + kAppActivated = 0x0, + kFrameTick = 0x1, + kRenderFinished = 0x2, + kCharTyped = 0x3, + kCursorVisibility = 0x5, + kShutdown = 0x6, + kScreenSizeChanged = 0x7, + kDriverError = 0x8, + kRecvPing = kDriverError, + kDpiChanged = 0xa, + kGamepadAttached = 0xb, + kGamepadState = 0xc, + kGamepadAxis = 0xd, + kGamepadButtonDown = 0xe, + kGamepadButtonUp = 0xf, + kCharTypedInjected = 0x13, + kKeyDown = 0x1a, + kKeyUp = 0x1b, + kFocusLost = 0x1c, + kMouseMoveRelative = 0x1d, + kMouseButtonDown = 0x1e, + kMouseLeave = 0x1f, + kMouseMove = 0x20, + kMouseButtonUp = 0x21, + kMouseWheel = 0x22, + kWindowMoved = 0x23, + kFriend = 0x24, + kService = 0x25, + kServiceAlt = 0x26, + kFocusGained = 0x27, + kWindowResized = 0x28, + kTouchDown = 0x2c, + kTouchMove = 0x2d, + kTouchUp = 0x2e, + kReserved = 0x2a, kNone = 0xffff, } + public enum FriendEventType : int + { + kStatusChanged = 0x26, + kLocationChanged = 0x28, + kFriendAddedOrRemoved = 0x2c, + } + + public enum Backend : uint + { + Unknown = 0, + D3D9, + GLES3, + } + public enum Metric : uint { Metric0, @@ -16115,11 +16352,11 @@ public enum Metric : uint Count, } - public enum Transform : int + public enum RendererMode : uint { - TRANSFORM_PROJECTION_MATRIX = 0, - TRANSFORM_MODEL_MATRIX = 1, - TRANSFORM_COUNT = 5, + Windowed = 0, + WindowedBorderless = 1, + WindowedFullscreen = 2, } [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0x138)] @@ -16215,7 +16452,7 @@ public unsafe struct Agent [global::System.Runtime.InteropServices.FieldOffset(0x0054)] public float RotationSin; // sine of rotation [global::System.Runtime.InteropServices.FieldOffset(0x0058)] - public uint NameProperties; // Bitmap basically telling what the agent is + public global::Daybreak.API.Interop.GWCA.GW.NameTagFlags NameProperties; // Bitmap basically telling what the agent is [global::System.Runtime.InteropServices.FieldOffset(0x005C)] public uint Ground; [global::System.Runtime.InteropServices.FieldOffset(0x0060)] @@ -17665,9 +17902,9 @@ public unsafe struct MapContext [global::System.Runtime.InteropServices.FieldOffset(0x0080)] public uint H0080; [global::System.Runtime.InteropServices.FieldOffset(0x0084)] - public nint Terrain; + public nint Terrain; // relationship to TerrainTexContext is unconfirmed [global::System.Runtime.InteropServices.FieldOffset(0x0088)] - public uint H0088; + public nint Collision; // "Collision" map chunk (Engine\Map\Collision\CollApi.cpp) [global::System.Runtime.InteropServices.FieldOffset(0x008C)] public global::Daybreak.API.Interop.GWCA.GW.Constants.MapID MapId; [global::System.Runtime.InteropServices.FieldOffset(0x0090)] @@ -17689,27 +17926,15 @@ public unsafe struct MapContext [global::System.Runtime.InteropServices.FieldOffset(0x00B0)] public uint H00B0; [global::System.Runtime.InteropServices.FieldOffset(0x00B4)] - public uint H00B4; - [global::System.Runtime.InteropServices.FieldOffset(0x00B8)] - public uint H00B8; - [global::System.Runtime.InteropServices.FieldOffset(0x00BC)] - public uint H00BC; + public global::Daybreak.API.Interop.GuildWars.Vec3fStruct ViewEye; // cached by GmWorldUpdateView [global::System.Runtime.InteropServices.FieldOffset(0x00C0)] - public uint H00C0; - [global::System.Runtime.InteropServices.FieldOffset(0x00C4)] - public uint H00C4; - [global::System.Runtime.InteropServices.FieldOffset(0x00C8)] - public uint H00C8; + public global::Daybreak.API.Interop.GuildWars.Vec3fStruct ViewTarget; [global::System.Runtime.InteropServices.FieldOffset(0x00CC)] - public uint H00CC; - [global::System.Runtime.InteropServices.FieldOffset(0x00D0)] - public uint H00D0; - [global::System.Runtime.InteropServices.FieldOffset(0x00D4)] - public uint H00D4; + public global::Daybreak.API.Interop.GuildWars.Vec3fStruct ViewUp; [global::System.Runtime.InteropServices.FieldOffset(0x00D8)] public uint H00D8; [global::System.Runtime.InteropServices.FieldOffset(0x00DC)] - public uint H00DC; + public uint ViewFlags; // bit 0: view has been updated this frame [global::System.Runtime.InteropServices.FieldOffset(0x00E0)] public uint H00E0; [global::System.Runtime.InteropServices.FieldOffset(0x00E4)] @@ -17717,7 +17942,7 @@ public unsafe struct MapContext [global::System.Runtime.InteropServices.FieldOffset(0x00E8)] public uint H00E8; [global::System.Runtime.InteropServices.FieldOffset(0x00EC)] - public uint H00EC; + public global::Daybreak.API.Interop.GuildWars.MapLightContext* Lights; // "Light" map chunk [global::System.Runtime.InteropServices.FieldOffset(0x00F0)] public uint H00F0; [global::System.Runtime.InteropServices.FieldOffset(0x00F4)] @@ -17731,7 +17956,7 @@ public unsafe struct MapContext [global::System.Runtime.InteropServices.FieldOffset(0x0104)] public uint H0104; [global::System.Runtime.InteropServices.FieldOffset(0x0108)] - public uint H0108; + public uint Flags; // bit 1: map has water [global::System.Runtime.InteropServices.FieldOffset(0x010C)] public uint H010C; [global::System.Runtime.InteropServices.FieldOffset(0x0110)] @@ -17743,19 +17968,49 @@ public unsafe struct MapContext [global::System.Runtime.InteropServices.FieldOffset(0x011C)] public uint H011C; [global::System.Runtime.InteropServices.FieldOffset(0x0120)] - public uint H0120; + public nint Shore; // "Shore" map chunk, only loaded when flags bit 1 is set [global::System.Runtime.InteropServices.FieldOffset(0x0124)] public uint H0124; [global::System.Runtime.InteropServices.FieldOffset(0x0128)] public uint H0128; [global::System.Runtime.InteropServices.FieldOffset(0x012C)] - public uint H012C; + public global::Daybreak.API.Interop.GuildWars.MapWaterContext* Water; [global::System.Runtime.InteropServices.FieldOffset(0x0130)] public nint Zones; [global::System.Runtime.InteropServices.FieldOffset(0x0134)] public uint H0134; } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0x20)] + public unsafe struct MapLightContext + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public global::Daybreak.API.Interop.GuildWars.GuildWarsArray Descriptors; + [global::System.Runtime.InteropServices.FieldOffset(0x0010)] + public global::Daybreak.API.Interop.GuildWars.GuildWarsArray Lights; // HGrLight handles, parallel to descriptors (same count) + } + + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0x1C)] + public unsafe struct MapLightDescriptor + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public global::Daybreak.API.Interop.GuildWars.Vec3fStruct Position; + [global::System.Runtime.InteropServices.FieldOffset(0x000C)] + public byte Red; + [global::System.Runtime.InteropServices.FieldOffset(0x000D)] + public byte Green; + [global::System.Runtime.InteropServices.FieldOffset(0x000E)] + public byte Blue; + [global::System.Runtime.InteropServices.FieldOffset(0x000F)] + public byte H000F; + [global::System.Runtime.InteropServices.FieldOffset(0x0010)] + public float Intensity; // colour bytes are premultiplied by intensity / 255 when the light is built + [global::System.Runtime.InteropServices.FieldOffset(0x0014)] + public float InnerRange; + [global::System.Runtime.InteropServices.FieldOffset(0x0018)] + public float OuterRange; + } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0x90)] public unsafe struct MapProp { @@ -17777,10 +18032,14 @@ public unsafe struct MapProp public float RotationAngle; [global::System.Runtime.InteropServices.FieldOffset(0x003C)] public float RotationCos; - [global::System.Runtime.InteropServices.FieldOffset(0x003C)] - public float RotationSin; [global::System.Runtime.InteropServices.FieldOffset(0x0040)] - public fixed uint H0034[5]; + public float RotationSin; + [global::System.Runtime.InteropServices.FieldOffset(0x0044)] + public fixed uint H0044[3]; + [global::System.Runtime.InteropServices.FieldOffset(0x0050)] + public float Scale; + [global::System.Runtime.InteropServices.FieldOffset(0x0054)] + public global::Daybreak.API.Interop.GuildWars.PropModelInfo* ModelInfo; [global::System.Runtime.InteropServices.FieldOffset(0x0058)] public global::Daybreak.API.Interop.GuildWars.RecObject* InteractiveModel; [global::System.Runtime.InteropServices.FieldOffset(0x005C)] @@ -17789,8 +18048,8 @@ public unsafe struct MapProp public uint AppearanceBitmap; // Modified when animation changes [global::System.Runtime.InteropServices.FieldOffset(0x0070)] public uint AnimationBits; - [global::System.Runtime.InteropServices.FieldOffset(0x0064)] - public fixed uint H0064[5]; + [global::System.Runtime.InteropServices.FieldOffset(0x0074)] + public fixed uint H0074[5]; [global::System.Runtime.InteropServices.FieldOffset(0x0088)] public global::Daybreak.API.Interop.GuildWars.PropByType* PropObjectInfo; [global::System.Runtime.InteropServices.FieldOffset(0x008C)] @@ -17805,6 +18064,27 @@ public unsafe struct MapTypeInstanceInfo public global::Daybreak.API.Interop.GWCA.GW.RegionType MapRegionType; } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1)] + public unsafe struct MapWaterContext + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public fixed uint H0000[43]; + [global::System.Runtime.InteropServices.FieldOffset(0x00AC)] + public uint SceneProgram; // the single handle water contributes to the scene program list + [global::System.Runtime.InteropServices.FieldOffset(0x00B0)] + public fixed uint H00B0[12]; + [global::System.Runtime.InteropServices.FieldOffset(0x00E0)] + public fixed uint ShaderPrograms[2]; // two of the four per-map shader programs, chosen by quality + [global::System.Runtime.InteropServices.FieldOffset(0x00E8)] + public uint H00E8; + [global::System.Runtime.InteropServices.FieldOffset(0x00EC)] + public float PlaneZ; // world height of the water plane + [global::System.Runtime.InteropServices.FieldOffset(0x00F0)] + public fixed uint H00F0[42]; + [global::System.Runtime.InteropServices.FieldOffset(0x0198)] + public fixed uint ShaderPrograms2[2]; // the other two quality variants + } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] public unsafe struct Mat4x3f { @@ -18401,13 +18681,11 @@ public unsafe struct PropModelInfo [global::System.Runtime.InteropServices.FieldOffset(0x0000)] public uint H0000; [global::System.Runtime.InteropServices.FieldOffset(0x0004)] - public uint H0004; + public nint ModelFileName; // hash-encoded; FileHashToFileId() turns it into a file id [global::System.Runtime.InteropServices.FieldOffset(0x0008)] - public uint H0008; + public float BoundingRadius; // model space; scale by MapProp::scale for world units [global::System.Runtime.InteropServices.FieldOffset(0x000C)] - public uint H000C; - [global::System.Runtime.InteropServices.FieldOffset(0x0010)] - public uint H0010; + public fixed float BoundsZOffsets[2]; [global::System.Runtime.InteropServices.FieldOffset(0x0014)] public uint H0014; } @@ -18696,6 +18974,45 @@ public unsafe struct TagInfo public ushort Level; } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0xC)] + public unsafe struct TerrainShadowRecord + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public nint CompressedStream; // layer 1, entropy coded, decoded per tile + [global::System.Runtime.InteropServices.FieldOffset(0x0004)] + public nint CompressedStreamEnd; + [global::System.Runtime.InteropServices.FieldOffset(0x0008)] + public nint BlockMask; // layer 2, the map's own data - not scratch + } + + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1)] + public unsafe struct TerrainTexTile + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public fixed uint H0000[3]; + [global::System.Runtime.InteropServices.FieldOffset(0x000C)] + public uint TileX; + [global::System.Runtime.InteropServices.FieldOffset(0x0010)] + public uint TileY; + [global::System.Runtime.InteropServices.FieldOffset(0x0014)] + public uint State; // set to 2 once the tile's shadow layer has been decoded + [global::System.Runtime.InteropServices.FieldOffset(0x0018)] + public uint H0018; // zeroed alongside the state above + } + + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1)] + public unsafe struct TerrainTexture + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public fixed uint H0000[39]; + [global::System.Runtime.InteropServices.FieldOffset(0x009C)] + public uint StreamFlags; // bit 0x800: decompress + upload still pending + [global::System.Runtime.InteropServices.FieldOffset(0x00A0)] + public fixed uint H00A0[55]; + [global::System.Runtime.InteropServices.FieldOffset(0x017C)] + public global::Daybreak.API.Interop.GuildWars.TerrainTexTile* Tile; // tagged pointer: null, or bit 0 set, means no tile + } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1)] public unsafe struct TextCache { @@ -19182,13 +19499,13 @@ public unsafe struct AgentNameTagInfo [global::System.Runtime.InteropServices.FieldOffset(0x0000)] public uint AgentId; [global::System.Runtime.InteropServices.FieldOffset(0x0004)] - public uint H0002; + public uint H0004; [global::System.Runtime.InteropServices.FieldOffset(0x0008)] - public uint H0003; + public uint H0008; [global::System.Runtime.InteropServices.FieldOffset(0x000C)] public nint NameEnc; [global::System.Runtime.InteropServices.FieldOffset(0x0010)] - public byte H0010; + public byte Highlight; [global::System.Runtime.InteropServices.FieldOffset(0x0011)] public byte H0012; [global::System.Runtime.InteropServices.FieldOffset(0x0012)] @@ -19209,6 +19526,10 @@ public unsafe struct AgentNameTagInfo public byte H001F; [global::System.Runtime.InteropServices.FieldOffset(0x0020)] public nint ExtraInfoEnc; // Title etc + [global::System.Runtime.InteropServices.FieldOffset(0x0024)] + public uint ExtraInfoColor; // ARGB + [global::System.Runtime.InteropServices.FieldOffset(0x0028)] + public uint ExtraInfoAttributes; // bold/size etc } [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] @@ -19533,10 +19854,10 @@ public unsafe struct kLogout [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] public unsafe struct kMeasureContent { - public float MaxWidth; // Maximum width constraint - public float MaxHeight; // Maximum height constraint - public float* SizeOutput; // Pointer to output buffer for calculated size - public uint Flags; // Layout flags (similar to the 0x100 flag we saw) + public float MaxWidth; // Available width, after the frame's own margin/padding is already subtracted + public float MaxHeight; // Available height, after the frame's own margin/padding is already subtracted + public float* SizeOutput; // Points at a 2-float [width, height] buffer the handler must fill + public uint Flags; // Not confirmed meaningful - see comment above } [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] @@ -19750,13 +20071,6 @@ public unsafe struct kSendPingWeaponSet public uint OffhandItemId; } - [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] - public unsafe struct kSendUseItem - { - public uint ItemId; - public ushort Quantity; // Unused, but would be cool - } - [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] public unsafe struct kSendWorldAction { @@ -19786,12 +20100,12 @@ public unsafe struct kSetAgentProfession [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] public unsafe struct kSetLayout { - public float Field0x0; - public float Field0x4; - public float Field0x8; - public float Field0xc; public float AvailableWidth; public float AvailableHeight; + public float LocalLeft; + public float LocalBottom; + public float LocalRight; + public float LocalTop; } [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] @@ -19902,12 +20216,62 @@ public unsafe struct kWriteToChatLogWithSender public nint SenderEnc; } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1, Size = 0xD0)] + public unsafe struct GlFragmentRenderState + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public fixed float TextureFactor[4]; + [global::System.Runtime.InteropServices.FieldOffset(0x0010)] + public fixed float FogColor[4]; + [global::System.Runtime.InteropServices.FieldOffset(0x0020)] + public fixed float SamplerBiases[2]; + [global::System.Runtime.InteropServices.FieldOffset(0x0040)] + public fixed float BumpEnvMat[8]; + [global::System.Runtime.InteropServices.FieldOffset(0x00C0)] + public fixed float DiscardSettings[4]; + } + + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit, Pack = 1)] + public unsafe struct GlesDevice + { + [global::System.Runtime.InteropServices.FieldOffset(0x0000)] + public fixed byte H0000[2004]; + [global::System.Runtime.InteropServices.FieldOffset(0x07D4)] + public uint DevMode; // GR_MODE_* + [global::System.Runtime.InteropServices.FieldOffset(0x07D8)] + public fixed byte H07D8[40]; + [global::System.Runtime.InteropServices.FieldOffset(0x0800)] + public nint DevWindow; // native window + [global::System.Runtime.InteropServices.FieldOffset(0x0804)] + public fixed byte H0804[8]; + [global::System.Runtime.InteropServices.FieldOffset(0x080C)] + public nint DevSurface; + [global::System.Runtime.InteropServices.FieldOffset(0x0810)] + public uint Width; // live surface size + [global::System.Runtime.InteropServices.FieldOffset(0x0814)] + public uint Height; + [global::System.Runtime.InteropServices.FieldOffset(0x0818)] + public nint DevContext; + [global::System.Runtime.InteropServices.FieldOffset(0x081C)] + public fixed byte H081C[2004]; + [global::System.Runtime.InteropServices.FieldOffset(0x0FF0)] + public global::Daybreak.API.Interop.GuildWars.GlFragmentRenderState FragmentState; + [global::System.Runtime.InteropServices.FieldOffset(0x10C0)] + public uint UniformBuffer; // the UBO name + } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] public unsafe struct PacketBase { public uint Header; } + [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)] + public unsafe struct HookTarget + { + public nuint Value; + } + public unsafe struct KeyCallback { public global::Daybreak.API.Interop.GuildWars.GuildWarsArray Value; } public unsafe struct AgentArray { public global::Daybreak.API.Interop.GuildWars.GuildWarsArray Value; } public unsafe struct AgentEffectsArray { public global::Daybreak.API.Interop.GuildWars.GuildWarsArray Value; } diff --git a/Daybreak.API/Interop/GuildWars/GuildWarsArray.cs b/Daybreak.API/Interop/GuildWars/GuildWarsArray.cs index 3676b202..a5d4bf20 100644 --- a/Daybreak.API/Interop/GuildWars/GuildWarsArray.cs +++ b/Daybreak.API/Interop/GuildWars/GuildWarsArray.cs @@ -13,17 +13,44 @@ namespace Daybreak.API.Interop.GuildWars; public readonly uint Size; public readonly uint Param; + /// + /// Upper bound on a believable element count. Guild Wars' largest arrays hold at most a + /// few thousand entries, so anything beyond this is uninitialised or misresolved memory + /// rather than a real array. + /// + private const uint MaxPlausibleCapacity = 0x10000; + + /// + /// Mirrors GW::BaseArray::valid() - the buffer must be null or aligned and the size + /// must fit within the capacity - plus a bound on the capacity itself. Guild Wars leaves + /// these fields transiently inconsistent while it (re)allocates an array, and a + /// misresolved GWCA scan can point this struct at arbitrary bytes that still satisfy + /// size <= capacity. + /// + public bool IsValid => + (this.Buffer is null || ((nuint)this.Buffer & 0x3) == 0) && + this.Size <= this.Capacity && + this.Capacity <= MaxPlausibleCapacity; + + /// + /// Number of elements that can safely be read. Zero whenever the array is not backed by + /// a buffer, matching GW::BaseArray::get(), which returns null instead of + /// dereferencing. Reading past this is an access violation, and because NativeAOT cannot + /// throw AccessViolationException it fail-fasts and takes Guild Wars down with it. + /// + public uint Count => this.Buffer is not null && this.IsValid ? this.Size : 0; + public T this[int index] { get { ArgumentOutOfRangeException.ThrowIfNegative(index); - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, this.Size); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, this.Count); return this.Buffer[index]; } } - public Enumerator GetEnumerator() => new(this.Buffer, this.Size); + public Enumerator GetEnumerator() => new(this.Buffer, this.Count); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); diff --git a/Daybreak.API/Interop/GuildWars/WrappedPointer.cs b/Daybreak.API/Interop/GuildWars/WrappedPointer.cs index 646d8f90..16691574 100644 --- a/Daybreak.API/Interop/GuildWars/WrappedPointer.cs +++ b/Daybreak.API/Interop/GuildWars/WrappedPointer.cs @@ -7,6 +7,18 @@ public readonly unsafe struct WrappedPointer(T* pointer) public bool IsNull => this.Pointer is null; + /// + /// True when the pointer is non-null and plausibly dereferenceable. + /// + /// + /// Guild Wars structures are 4-byte aligned on x86, so a misaligned address is never a + /// real structure. When a GWCA pattern scan fails to resolve, it can hand back an + /// address inside Gw.exe's code section (for example 0x0048CA72); reading through that + /// yields nonsense field values and eventually an access violation. NativeAOT cannot + /// throw AccessViolationException, so it fail-fasts and terminates Guild Wars. + /// + public bool IsValid => this.Pointer is not null && ((nuint)this.Pointer & 0x3) == 0; + public static implicit operator WrappedPointer(T* pointer) => new(pointer); public static implicit operator T*(WrappedPointer wrappedPointer) => wrappedPointer.Pointer; diff --git a/Daybreak.API/Services/CharacterSelectService.cs b/Daybreak.API/Services/CharacterSelectService.cs index ddd127a5..ec104251 100644 --- a/Daybreak.API/Services/CharacterSelectService.cs +++ b/Daybreak.API/Services/CharacterSelectService.cs @@ -35,21 +35,28 @@ public sealed class CharacterSelectService( { var gameContext = this.gameContextService.GetGameContext(); if (gameContext.IsNull || - gameContext.Pointer->World is null) + gameContext.Pointer->World is null || + gameContext.Pointer->Character is null) { scopedLogger.LogError("Game context is not initialized"); return default; } var availableCharsContext = this.gameContextService.GetAvailableChars(); - if (availableCharsContext.IsNull) + if (!availableCharsContext.IsValid || + !availableCharsContext.Pointer->IsValid) { - scopedLogger.LogError("Available characters context is not initialized"); + scopedLogger.LogError( + "Available characters context is not initialized (array@0x{Array:X8} buffer=0x{Buffer:X8} capacity={Capacity} size={Size})", + (nuint)availableCharsContext.Pointer, + availableCharsContext.IsValid ? (nuint)availableCharsContext.Pointer->Buffer : 0, + availableCharsContext.IsValid ? availableCharsContext.Pointer->Capacity : 0, + availableCharsContext.IsValid ? availableCharsContext.Pointer->Size : 0); return default; } var currentUuid = (*(Uuid*)gameContext.Pointer->Character->PlayerUuid).ToString(); - var availableChars = new List((int)availableCharsContext.Pointer->Size); + var availableChars = new List((int)availableCharsContext.Pointer->Count); foreach (var charContext in *availableCharsContext.Pointer) { var name = new string(charContext.Name); @@ -196,7 +203,9 @@ private async Task SelectCharacterToPlay(string characterName, bool play, // Find target character index uint targetIdx = 0xFFFF; - var charCount = ctx.Pointer->Chars.Size; + // Count (not Size) so a not-yet-allocated roster yields zero iterations + // instead of indexing through a null buffer. + var charCount = ctx.Pointer->Chars.Count; for (uint i = 0; i < charCount; i++) { diff --git a/Daybreak.Core/Services/DXVK/DXVKService.cs b/Daybreak.Core/Services/DXVK/DXVKService.cs index a0e4e51c..0caed3a5 100644 --- a/Daybreak.Core/Services/DXVK/DXVKService.cs +++ b/Daybreak.Core/Services/DXVK/DXVKService.cs @@ -433,6 +433,7 @@ private async Task DownloadAndExtractDXVK(IProgress progre { Directory.Delete(x32Target, recursive: true); } + if (Directory.Exists(x64Target)) { Directory.Delete(x64Target, recursive: true); diff --git a/Daybreak.Core/Views/EventCalendarView.razor.cs b/Daybreak.Core/Views/EventCalendarView.razor.cs index 72a50b9b..382a5ffe 100644 --- a/Daybreak.Core/Views/EventCalendarView.razor.cs +++ b/Daybreak.Core/Views/EventCalendarView.razor.cs @@ -6,6 +6,7 @@ using TrailBlazr.ViewModels; namespace Daybreak.Views; + public sealed class EventCalendarViewModel(IEventService eventService) : ViewModelBase { @@ -92,7 +93,7 @@ public List GetCalendarDates() { dates.Add(new DateTime(today.Year, today.Month, day)); } - + return dates; } @@ -107,6 +108,7 @@ public List GetEventsForDate(DateTime date) events.Add(eventItem); } } + return events; } } diff --git a/Daybreak.Generators/CppHeaderParser.cs b/Daybreak.Generators/CppHeaderParser.cs index 016d2ac0..d7c08c8d 100644 --- a/Daybreak.Generators/CppHeaderParser.cs +++ b/Daybreak.Generators/CppHeaderParser.cs @@ -178,6 +178,7 @@ public static void Parse(string headerText, ConstantsNode root) { inMultiLineConstexpr = false; } + continue; } @@ -189,6 +190,7 @@ public static void Parse(string headerText, ConstantsNode root) { inSkipBlock = false; } + continue; } @@ -211,6 +213,7 @@ public static void Parse(string headerText, ConstantsNode root) { ParseEnumMembersFromLine(trimmed.Substring(0, closeBraceIdx), currentEnum); } + inEnum = false; var node = namespaceStack.Peek(); node.Enums.Add(currentEnum); @@ -231,11 +234,11 @@ public static void Parse(string headerText, ConstantsNode root) int closeBraces = CountChar(lineWithoutComments, '}'); int prevDepth = structBraceDepth; structBraceDepth += openBraces - closeBraces; - + // Debug: Track brace changes for AgentLiving if (currentStruct.Name == "AgentLiving" && (openBraces > 0 || closeBraces > 0)) { - DebugLines.Add($"[BRACE] line {i+1}: {currentStruct.Name} depth {prevDepth}->{structBraceDepth} (open={openBraces}, close={closeBraces})"); + DebugLines.Add($"[BRACE] line {i + 1}: {currentStruct.Name} depth {prevDepth}->{structBraceDepth} (open={openBraces}, close={closeBraces})"); } // If we hit the closing brace of the struct @@ -252,8 +255,8 @@ public static void Parse(string headerText, ConstantsNode root) pathParts.Add(n.Name); pathParts.Reverse(); currentStruct.DebugNamespacePath = string.Join(".", pathParts); - - DebugLines.Add($"[STRUCT-END] line {i+1}: {currentStruct.Name} with {currentStruct.Fields.Count} fields"); + + DebugLines.Add($"[STRUCT-END] line {i + 1}: {currentStruct.Name} with {currentStruct.Fields.Count} fields"); node.Structs.Add(currentStruct); currentStruct = null; inStruct = false; @@ -278,6 +281,7 @@ public static void Parse(string headerText, ConstantsNode root) if (field is not null) currentStruct.Fields.Add(field); } + continue; } @@ -295,7 +299,7 @@ public static void Parse(string headerText, ConstantsNode root) var trailingCommentIdx = trimmed.IndexOf("//"); if (trailingCommentIdx > 0) trimmedWithoutTrailingComment = trimmed.Substring(0, trailingCommentIdx).TrimEnd(); - + if (trimmedWithoutTrailingComment.Contains("(") && (trimmedWithoutTrailingComment.Contains(")") || trimmedWithoutTrailingComment.Contains("{"))) continue; @@ -339,6 +343,7 @@ public static void Parse(string headerText, ConstantsNode root) }; currentStruct.Fields.Add(field); } + continue; } @@ -354,6 +359,7 @@ public static void Parse(string headerText, ConstantsNode root) var child = current.GetOrCreateChild(part); namespaceStack.Push(child); } + continue; } @@ -366,13 +372,14 @@ public static void Parse(string headerText, ConstantsNode root) freeBraceDepth--; continue; } - + if (namespaceStack.Count > 1) { // Debug: Track where namespace was popped var poppedNode = namespaceStack.Pop(); poppedNode.DebugPopLine = i + 1; } + continue; } @@ -390,6 +397,7 @@ public static void Parse(string headerText, ConstantsNode root) } } } + continue; } @@ -436,6 +444,7 @@ public static void Parse(string headerText, ConstantsNode root) inSkipBlock = true; skipBlockBraceCount = braces; } + continue; } @@ -448,6 +457,7 @@ public static void Parse(string headerText, ConstantsNode root) inSkipBlock = true; skipBlockBraceCount = braces; } + continue; } @@ -455,17 +465,18 @@ public static void Parse(string headerText, ConstantsNode root) if (OutOfLineMethodRegex.IsMatch(trimmed)) { int braces = CountChar(trimmed, '{') - CountChar(trimmed, '}'); - DebugLines.Add($"[OUT-OF-LINE] line {i+1}: {trimmed.Substring(0, Math.Min(50, trimmed.Length))}... braces={braces}"); + DebugLines.Add($"[OUT-OF-LINE] line {i + 1}: {trimmed.Substring(0, Math.Min(50, trimmed.Length))}... braces={braces}"); if (braces > 0) { inSkipBlock = true; skipBlockBraceCount = braces; } + continue; } else if (trimmed.Contains("::") && trimmed.Contains("(") && trimmed.Contains("{")) { - DebugLines.Add($"[UNMATCHED-METHOD] line {i+1}: {trimmed.Substring(0, Math.Min(60, trimmed.Length))}"); + DebugLines.Add($"[UNMATCHED-METHOD] line {i + 1}: {trimmed.Substring(0, Math.Min(60, trimmed.Length))}"); } // ── Track function declarations with body on separate line ─ @@ -492,6 +503,7 @@ public static void Parse(string headerText, ConstantsNode root) inMultiLineConstexpr = true; multiLineBraceCount = braces; } + continue; } @@ -501,7 +513,7 @@ public static void Parse(string headerText, ConstantsNode root) { var structName = structMatch.Groups[1].Value; var baseType = structMatch.Groups[2].Success ? structMatch.Groups[2].Value : null; - DebugLines.Add($"[STRUCT-START] line {i+1}: {structName} (inSkip={inSkipBlock}, freeBrace={freeBraceDepth})"); + DebugLines.Add($"[STRUCT-START] line {i + 1}: {structName} (inSkip={inSkipBlock}, freeBrace={freeBraceDepth})"); // Skip some special cases if (structName.StartsWith("__") || structName == "Packet") @@ -512,6 +524,7 @@ public static void Parse(string headerText, ConstantsNode root) inSkipBlock = true; skipBlockBraceCount = braces; } + continue; } @@ -530,7 +543,7 @@ public static void Parse(string headerText, ConstantsNode root) if (structNobraceMatch.Success) { var structName = structNobraceMatch.Groups[1].Value; - + // Skip forward declarations (just struct Name;) if (trimmed.EndsWith(";")) continue; @@ -559,8 +572,10 @@ public static void Parse(string headerText, ConstantsNode root) i = j; break; } + break; // unexpected content } + continue; } @@ -593,9 +608,11 @@ public static void Parse(string headerText, ConstantsNode root) { ParseEnumMembersFromLine(rest, enumDef); } + currentEnum = enumDef; inEnum = true; } + continue; } @@ -637,14 +654,18 @@ public static void Parse(string headerText, ConstantsNode root) { ParseEnumMembersFromLine(rest, enumDef); } + currentEnum = enumDef; inEnum = true; } + i = j; break; } + break; // unexpected content } + continue; } @@ -794,7 +815,7 @@ private static string StripComments(string line) int slashSlash = line.IndexOf("//"); if (slashSlash >= 0) line = line.Substring(0, slashSlash); - + // Remove /* */ style comments (simple - doesn't handle nested) int blockStart = line.IndexOf("/*"); while (blockStart >= 0) @@ -806,7 +827,7 @@ private static string StripComments(string line) line = line.Substring(0, blockStart); blockStart = line.IndexOf("/*"); } - + return line; } diff --git a/Daybreak.Generators/GenerateGWCABindings.cs b/Daybreak.Generators/GenerateGWCABindings.cs index dfef5838..6127eca6 100644 --- a/Daybreak.Generators/GenerateGWCABindings.cs +++ b/Daybreak.Generators/GenerateGWCABindings.cs @@ -238,7 +238,7 @@ private static void EmitSource( // Emit var sb = new StringBuilder(65536); EmitHeader(sb, totalExports, skippedExports); - + // Add diagnostic comment about parsed structs sb.AppendLine(" // ═══════════════════════════════════════════════════"); sb.AppendLine(" // Parsed structs diagnostic:"); @@ -246,6 +246,7 @@ private static void EmitSource( { sb.AppendLine($" // {diag}"); } + sb.AppendLine(" // ═══════════════════════════════════════════════════"); sb.AppendLine(); @@ -270,31 +271,33 @@ private static void EmitSource( { EmitConstantsNode(sb, grandchild, typeMap, namespaceClassNames, 4); } + continue; } + EmitConstantsNode(sb, child, typeMap, namespaceClassNames, 4); } sb.AppendLine("}"); // Close GWCA class sb.AppendLine("}"); // Close Daybreak.API.Interop namespace - + // Emit structs and enums into GuildWars namespace for consumer code compatibility // Consumer code uses `using Daybreak.API.Interop.GuildWars;` to access types sb.AppendLine(); sb.AppendLine("namespace Daybreak.API.Interop.GuildWars"); sb.AppendLine("{"); var emittedNames = new HashSet(StringComparer.Ordinal); - + // Collect inline array types needed by struct fields (for non-blittable array fields) var inlineArrayTypes = new HashSet<(string csType, int size)>(); CollectInlineArrayTypes(headerRoot, typeMap, inlineArrayTypes); - + // Emit manually-defined helper structs first (TLink, etc.) EmitManualHelperStructs(sb, 4, emittedNames); - + // Emit inline array types for non-blittable arrays (e.g., enum arrays) EmitInlineArrayTypes(sb, 4, inlineArrayTypes, emittedNames); - + // Emit enums first - they're often referenced by structs (e.g. Attribute enum vs Attribute struct) EmitEnumsToGuildWarsNamespace(sb, headerRoot, 4, emittedNames); EmitStructsToGuildWarsNamespace(sb, headerRoot, typeMap, 4, emittedNames, inlineArrayTypes); @@ -551,6 +554,7 @@ private static void EmitConstantsNode( else sb.AppendLine($"{innerPad} {SanitizeIdentifier(member.Name)},{comment}"); } + sb.AppendLine($"{innerPad}}}"); } else @@ -666,10 +670,10 @@ private static void CollectStructMappings( // Only add structs that can actually be emitted if (!CanEmitStruct(structDef)) continue; - + // Determine the C# name, handling collisions with namespace classes or enums var csName = SanitizeIdentifier(structDef.Name); - + // Check if name collides with a namespace class, enum, or ANOTHER struct already using this name if (namespaceClassNames.Contains(structDef.Name)) { @@ -693,21 +697,21 @@ private static void CollectStructMappings( else csName += "_"; } - + usedGuildWarsNames.Add(csName); - + // Use a qualified key that includes the C++ path to distinguish structs with the same name // from different namespaces (e.g., GW::Attribute vs GW::SkillbarMgr::Attribute) var mapKey = structDef.Name; var fqCsType = "global::Daybreak.API.Interop.GuildWars." + csName; var simpleStructKey = "struct " + structDef.Name; - + if (typeMap.ContainsKey(mapKey)) { // Use qualified key to distinguish this struct from enum or another struct // Include the csPath to make it unique (e.g., "struct GW.Attribute" vs "struct GW.SkillbarMgr.Attribute") mapKey = "struct " + csPath.Replace("GWCA.", "") + "." + structDef.Name; - + // Also add simple "struct X" key if not already taken (for field type resolution) // This allows MapCppFieldTypeToCs to find the struct without knowing the full path if (!typeMap.ContainsKey(simpleStructKey)) @@ -715,7 +719,7 @@ private static void CollectStructMappings( typeMap[simpleStructKey] = fqCsType; } } - + // Structs go into GuildWars namespace (flat), not nested GWCA classes typeMap[mapKey] = fqCsType; } @@ -745,19 +749,20 @@ private static void CollectNamespaceClassNames(NamespaceNode node, HashSet diagnostics) { var currentPath = string.IsNullOrEmpty(path) ? node.Name : path + "." + node.Name; - + // Show namespace pop line info if (node.DebugPopLine > 0) { diagnostics.Add($"[NAMESPACE] {currentPath} popped at line {node.DebugPopLine}"); } - + foreach (var structDef in node.Structs) { var (canEmit, reason) = CanEmitStructWithReason(structDef); var status = canEmit ? "OK" : $"SKIP: {reason}"; diagnostics.Add($"{currentPath}.{structDef.Name}: {structDef.Fields.Count} fields [{status}]"); } + foreach (var child in node.Children.Values) { CollectStructDiagnostics(child, currentPath, diagnostics); @@ -772,7 +777,7 @@ private static (bool canEmit, string? reason) CanEmitStructWithReason(CppStructD // Skip structs with no fields (usually forward declarations that got parsed) if (structDef.Fields.Count == 0) return (false, "no fields"); - + // Check for mixed offset/no-offset fields (can't use Explicit layout if not all have offsets) bool hasAnyOffset = structDef.Fields.Any(f => f.Offset.HasValue); bool allHaveOffsets = structDef.Fields.All(f => f.Offset.HasValue); @@ -788,7 +793,7 @@ private static (bool canEmit, string? reason) CanEmitStructWithReason(CppStructD return (false, $"template param T in field {field.Name}"); // Skip structs with complex template containers we can't represent // Note: TLink is handled separately in MapCppFieldTypeToCs (8-byte linked list node) - if (cppType.Contains("TList<") || + if (cppType.Contains("TList<") || cppType.Contains("PrioQ<") || cppType.Contains("PrioQLink<") || cppType.Contains("BaseArray<")) return (false, $"complex template in field {field.Name}: {cppType}"); @@ -815,9 +820,10 @@ private static (bool canEmit, string? reason) CanEmitStructWithReason(CppStructD if (cppType.Contains("EquipmentVTable") || cppType.Contains("VTable")) return (false, $"vtable in field {field.Name}: {cppType}"); } + return (true, null); } - + private static bool CanEmitStruct(CppStructDef structDef) => CanEmitStructWithReason(structDef).canEmit; /// @@ -907,7 +913,7 @@ private static void EmitStructsToGuildWarsNamespace(StringBuilder sb, ConstantsN // Start with the node's own name as the path base (GWCA for headerRoot) EmitStructsRecursive(sb, node, node.Name, typeMap, indent, emittedNames, inlineArrayTypes); } - + private static void EmitStructsRecursive(StringBuilder sb, ConstantsNode node, string currentPath, Dictionary typeMap, int indent, HashSet emittedNames, HashSet<(string csType, int size)> inlineArrayTypes) { foreach (var structDef in node.Structs.OrderBy(s => s.Name, StringComparer.Ordinal)) @@ -915,13 +921,13 @@ private static void EmitStructsRecursive(StringBuilder sb, ConstantsNode node, s // Skip structs that can't be emitted if (!CanEmitStruct(structDef)) continue; - + // Get the C# name from typeMap (which includes collision-resolution suffixes like "Struct") // Try direct name first, then qualified struct key (for collision cases) string? fqCsName; var directLookup = typeMap.TryGetValue(structDef.Name, out fqCsName); var containsGuildWars = fqCsName?.Contains("GuildWars.") ?? false; - + if (!directLookup || !containsGuildWars) { // If direct lookup failed or returned an enum (not in GuildWars namespace), @@ -929,22 +935,22 @@ private static void EmitStructsRecursive(StringBuilder sb, ConstantsNode node, s var qualifiedKey = "struct " + currentPath.Replace("GWCA.", "") + "." + structDef.Name; typeMap.TryGetValue(qualifiedKey, out fqCsName); } - + if (fqCsName is null) continue; - + // Extract just the struct name from the fully-qualified name // e.g. "global::Daybreak.API.Interop.GuildWars.ItemStruct" -> "ItemStruct" var csName = fqCsName.Substring(fqCsName.LastIndexOf('.') + 1); - + // Skip duplicates (including if an enum with the same name was emitted) if (emittedNames.Contains(csName)) continue; - + emittedNames.Add(csName); EmitStruct(sb, structDef, typeMap, indent, inlineArrayTypes, csName); } - + // Recurse into children, appending the child's name to the current path foreach (var child in node.Children.Values) { @@ -959,7 +965,7 @@ private static void EmitStructsRecursive(StringBuilder sb, ConstantsNode node, s private static void EmitManualHelperStructs(StringBuilder sb, int indent, HashSet emittedNames) { var pad = new string(' ', indent); - + // TLink - doubly-linked list node used in Agent and other structs // C++ definition: struct TLink { TLink* prev_link; T* next_node; } // Size: 8 bytes (2 pointers on x86) @@ -1003,16 +1009,16 @@ private static void CollectInlineArrayTypes(ConstantsNode node, Dictionary inlineArrayTypes, HashSet emittedNames) { var pad = new string(' ', indent); - + foreach (var (csType, size) in inlineArrayTypes.OrderBy(x => x.csType).ThenBy(x => x.size)) { // Generate a name like "AttributeArray12" or "SkillIDArray8" var simpleName = GetSimpleTypeName(csType); var arrayTypeName = $"{simpleName}Array{size}"; - + if (!emittedNames.Add(arrayTypeName)) continue; - + sb.AppendLine($"{pad}/// "); sb.AppendLine($"{pad}/// Inline array of {size} {simpleName} elements."); sb.AppendLine($"{pad}/// "); @@ -1067,10 +1073,10 @@ private static string GetSimpleTypeName(string csType) var isPointer = csType.EndsWith("*"); if (isPointer) csType = csType.TrimEnd('*'); - + var lastDot = csType.LastIndexOf('.'); var name = lastDot >= 0 ? csType.Substring(lastDot + 1) : csType; - + // Append Ptr for pointer types to make valid identifier return isPointer ? name + "Ptr" : name; } @@ -1092,30 +1098,30 @@ private static void EmitEnumsToGuildWarsNamespace(StringBuilder sb, ConstantsNod { EmitEnumsRecursive(sb, node, indent, emittedNames); } - + private static void EmitEnumsRecursive(StringBuilder sb, ConstantsNode node, int indent, HashSet emittedNames) { var pad = new string(' ', indent); - + foreach (var enumDef in node.Enums.OrderBy(e => e.Name, StringComparer.Ordinal)) { // Skip anonymous enums if (enumDef.Name is null) continue; - + // Skip duplicates (including if a struct with the same name was emitted) if (emittedNames.Contains(enumDef.Name)) continue; - + emittedNames.Add(enumDef.Name); - + // Determine base type and map C++ types to C# var baseType = MapCppEnumBaseType(enumDef.UnderlyingType ?? "int"); - + sb.AppendLine(); sb.AppendLine($"{pad}public enum {SanitizeIdentifier(enumDef.Name)} : {baseType}"); sb.AppendLine($"{pad}{{"); - + var innerPad = new string(' ', indent + 4); foreach (var member in enumDef.Members) { @@ -1124,9 +1130,10 @@ private static void EmitEnumsRecursive(StringBuilder sb, ConstantsNode node, int else sb.AppendLine($"{innerPad}{SanitizeIdentifier(member.Name)},"); } + sb.AppendLine($"{pad}}}"); } - + // Recurse into children foreach (var child in node.Children.Values) { @@ -1141,19 +1148,19 @@ private static void EmitTypeAliasesToGuildWarsNamespace(StringBuilder sb, Consta { EmitTypeAliasesRecursive(sb, node, typeMap, indent, emittedNames); } - + private static void EmitTypeAliasesRecursive(StringBuilder sb, ConstantsNode node, Dictionary typeMap, int indent, HashSet emittedNames) { var pad = new string(' ', indent); - + foreach (var alias in node.TypeAliases.OrderBy(a => a.AliasName, StringComparer.Ordinal)) { // Skip duplicates if (emittedNames.Contains(alias.AliasName)) continue; - + emittedNames.Add(alias.AliasName); - + var innerType = alias.TemplateArg.Replace("::", ".").Trim(); // Handle pointer types (e.g., "Item *" -> "nint") if (innerType.EndsWith("*")) @@ -1170,10 +1177,10 @@ private static void EmitTypeAliasesRecursive(StringBuilder sb, ConstantsNode nod // Unmapped struct type - use nint as fallback innerType = "nint"; } - + sb.AppendLine($"{pad}public unsafe struct {alias.AliasName} {{ public global::Daybreak.API.Interop.GuildWars.GuildWarsArray<{innerType}> Value; }}"); } - + // Recurse into children foreach (var child in node.Children.Values) { @@ -1259,6 +1266,7 @@ private static string MapCppFieldTypeToCs(string cppType, Dictionary"; } + return "global::Daybreak.API.Interop.GuildWars.GuildWarsArray"; } @@ -1286,7 +1294,7 @@ private static string MapCppFieldTypeToCs(string cppType, Dictionary "nint", "HANDLE" => "nint", "HWND" => "nint", + // EGL handles: `using EGLSurface = void*;` aliases declared inside + // GWCA/Managers/RenderMgr.h, which the header parser doesn't resolve. + "EGLSurface" => "nint", + "EGLContext" => "nint", + "EGLDisplay" => "nint", "uintptr_t" => "nuint", "intptr_t" => "nint", "Vec2f" => "global::System.Numerics.Vector2", @@ -1420,6 +1433,7 @@ private static string ToPascalCase(string name) result.Append(part.Substring(1)); } } + return result.ToString(); } @@ -1649,7 +1663,7 @@ private static string ResolveTemplateArgType(DemangledType arg, Dictionary "nint", }; } - + /// /// Resolves a struct name to its C# type, handling name collisions with enums. /// @@ -1664,7 +1678,7 @@ private static string ResolveStructType(string name, Dictionary // Unmapped struct -> nint return "nint"; } - + /// /// Resolves a pointer inner type (the pointee) for struct or enum types, /// handling name collisions between structs and enums. @@ -1680,7 +1694,7 @@ private static string ResolveStructOrEnumPointerInner(string name, Dictionary /// Resolves a pointer type used as a template argument (e.g., Agent* in Array<Agent*>). /// In unsafe C#, pointer types can be used as generic type arguments for unmanaged structs. @@ -1689,17 +1703,17 @@ private static string ResolvePointerTemplateArg(DemangledType arg, Dictionary "nint", // unmapped pointer -> nint }; } - + /// /// Maps C/C++ primitive type names to C# primitive types. /// @@ -1890,6 +1904,7 @@ public ConstantsNode GetOrCreateChild(string childName) child = new ConstantsNode(childName); this.Children[childName] = child; } + return child; } } @@ -1943,4 +1958,4 @@ internal sealed class CppTypeAlias public string SourceType { get; set; } = ""; // e.g., "Array" public string? TemplateArg { get; set; } // e.g., "Buff" } -} \ No newline at end of file +} diff --git a/Daybreak.Generators/MsvcDemangler.cs b/Daybreak.Generators/MsvcDemangler.cs index 46b17891..b32ad503 100644 --- a/Daybreak.Generators/MsvcDemangler.cs +++ b/Daybreak.Generators/MsvcDemangler.cs @@ -461,6 +461,7 @@ private static void ReadFuncPtr(ReadOnlySpan s, ref int pos, List { pos++; // skip Z - this terminates the function pointer, not the outer param list } + break; } diff --git a/Daybreak.Linux/Services/Keyboard/KeyboardHookService.cs b/Daybreak.Linux/Services/Keyboard/KeyboardHookService.cs index ea7c1a71..a0c0f812 100644 --- a/Daybreak.Linux/Services/Keyboard/KeyboardHookService.cs +++ b/Daybreak.Linux/Services/Keyboard/KeyboardHookService.cs @@ -348,6 +348,7 @@ private static nint FindGtkWindowForCurrentProcess() result = window; break; } + current = NativeMethods.G_list_next(current); } diff --git a/Daybreak.Linux/Services/Screens/ScreenManager.cs b/Daybreak.Linux/Services/Screens/ScreenManager.cs index 6abe273b..30348392 100644 --- a/Daybreak.Linux/Services/Screens/ScreenManager.cs +++ b/Daybreak.Linux/Services/Screens/ScreenManager.cs @@ -221,7 +221,7 @@ private Rectangle EnsurePositionIsOnScreen(Rectangle position) /// global value, and under the forced X11 backend GDK cannot report reliable /// per-monitor scales anyway. /// - private int GetDpiForPosition(int x, int y) + private int GetDpiForPosition(int _, int __) { return (int)Math.Round(this.GetEffectiveScale() * DefaultDpi); } diff --git a/Daybreak.Linux/Services/Window/WindowManipulationService.cs b/Daybreak.Linux/Services/Window/WindowManipulationService.cs index ba906aa3..80977fe6 100644 --- a/Daybreak.Linux/Services/Window/WindowManipulationService.cs +++ b/Daybreak.Linux/Services/Window/WindowManipulationService.cs @@ -137,6 +137,7 @@ private static nint FindGtkWindowForCurrentProcess() result = window; break; } + current = NativeMethods.G_list_next(current); } diff --git a/Daybreak.Linux/Services/Wine/IWinePrefixManager.cs b/Daybreak.Linux/Services/Wine/IWinePrefixManager.cs index e04d8df0..e162164c 100644 --- a/Daybreak.Linux/Services/Wine/IWinePrefixManager.cs +++ b/Daybreak.Linux/Services/Wine/IWinePrefixManager.cs @@ -30,6 +30,13 @@ public interface IWinePrefixManager : IModService /// string GetWinePrefixPath(); + /// + /// Configures the Windows computer name from the Linux host name. + /// + /// Cancellation token. + /// True if successful, false otherwise. + Task ConfigureComputerName(CancellationToken cancellationToken); + /// /// Launches a Windows executable through Wine with the managed prefix. /// Uses event-based stdout/stderr reading to avoid pipe deadlocks. diff --git a/Daybreak.Linux/Services/Wine/WineDebugLog.cs b/Daybreak.Linux/Services/Wine/WineDebugLog.cs new file mode 100644 index 00000000..47305c81 --- /dev/null +++ b/Daybreak.Linux/Services/Wine/WineDebugLog.cs @@ -0,0 +1,96 @@ +using System.Diagnostics; +using Daybreak.Shared.Utils; + +namespace Daybreak.Linux.Services.Wine; + +/// +/// Debug-build support for capturing Wine diagnostics, most importantly the +/// winedbg --auto backtrace produced when Guild Wars crashes. +/// +/// Guild Wars is launched by Daybreak.Injector.exe, so it is a *grandchild* of the +/// Wine process Daybreak starts and inherits that process's stderr. Daybreak stops +/// reading that pipe as soon as the injector prints its result, which means a later +/// crash backtrace is written into a pipe nobody drains: the output is lost, and if +/// the pipe buffer fills, the game blocks inside a stderr write. +/// +/// Redirecting stderr to a file inside the shell makes the descriptor outlive +/// Daybreak's interest in the process, so every descendant (injector, Gw.exe, +/// winedbg) appends to a durable log instead. +/// +internal static class WineDebugLog +{ + private const string LogFileName = "wine-debug.log"; + private const string EnableVariable = "DAYBREAK_WINE_DEBUG"; + private const string ChannelsVariable = "DAYBREAK_WINE_DEBUG_CHANNELS"; + + /// + /// Enabled by default for Debug builds. Either build configuration can opt in or + /// out explicitly with DAYBREAK_WINE_DEBUG=1 / =0. + /// + public static bool IsEnabled => + Environment.GetEnvironmentVariable(EnableVariable) switch + { + "1" or "true" or "TRUE" => true, + "0" or "false" or "FALSE" => false, +#if DEBUG + _ => true, +#else + _ => false, +#endif + }; + + public static string LogPath => PathUtils.GetAbsolutePathFromRoot(LogFileName); + + /// + /// Extra WINEDEBUG channels. Left unset by default: Wine's default err class + /// already reports unhandled exceptions and drives winedbg, while trace channels such + /// as +seh or +relay slow the game to a crawl. Set + /// DAYBREAK_WINE_DEBUG_CHANNELS=+seh when a specific investigation needs them. + /// + public static string? Channels => + Environment.GetEnvironmentVariable(ChannelsVariable) is { Length: > 0 } channels + ? channels + : null; + + /// + /// Rewrites to run the original command under + /// /bin/sh with stderr appended to . + /// + /// + /// The command is passed as a single positional parameter and re-parsed with + /// eval so the caller's existing double-quoting (paths such as + /// "Z:\...\Guild Wars\Gw.exe" contain spaces) keeps working unchanged. + /// + public static void Apply(ProcessStartInfo startInfo) + { + var logPath = LogPath; + var command = $"\"{startInfo.FileName}\" {startInfo.Arguments}"; + + startInfo.FileName = "/bin/sh"; + startInfo.Arguments = string.Empty; + startInfo.ArgumentList.Add("-c"); + startInfo.ArgumentList.Add("log=\"$1\"; shift; eval \"exec $* 2>>'$log'\""); + startInfo.ArgumentList.Add("daybreak-wine"); + startInfo.ArgumentList.Add(logPath); + startInfo.ArgumentList.Add(command); + + if (Channels is { } channels) + { + startInfo.Environment["WINEDEBUG"] = channels; + } + } + + public static void WriteSessionHeader(string description) + { + try + { + File.AppendAllText( + LogPath, + $"{Environment.NewLine}===== {DateTime.Now:yyyy-MM-dd HH:mm:ss} {description} ====={Environment.NewLine}"); + } + catch + { + // Diagnostics only - never fail a launch because the log is unwritable. + } + } +} diff --git a/Daybreak.Linux/Services/Wine/WinePrefixManager.cs b/Daybreak.Linux/Services/Wine/WinePrefixManager.cs index 7e31a9df..2b1deefa 100644 --- a/Daybreak.Linux/Services/Wine/WinePrefixManager.cs +++ b/Daybreak.Linux/Services/Wine/WinePrefixManager.cs @@ -19,6 +19,11 @@ public sealed class WinePrefixManager( { private const string WinePrefixFolder = "WinePrefix"; private const string WineExecutable = "wine"; + private const string WineNetworkRegistryKey = @"HKCU\Software\Wine\Network"; + private const string ComputerNameRegistryKey = + @"HKLM\System\CurrentControlSet\Control\ComputerName\ComputerName"; + private const string ActiveComputerNameRegistryKey = + @"HKLM\System\CurrentControlSet\Control\ComputerName\ActiveComputerName"; private static readonly ProgressUpdate ProgressStarting = new(0, "Starting Wine prefix setup"); private static readonly ProgressUpdate ProgressCheckingWine = new( @@ -117,7 +122,10 @@ public IProgressAsyncOperation PerformUninstallation(CancellationToken can public IEnumerable GetCustomArguments() => []; /// - public Task OnGuildWarsStarting(GuildWarsStartingContext guildWarsStartingContext, CancellationToken cancellationToken) + public async Task OnGuildWarsStarting( + GuildWarsStartingContext guildWarsStartingContext, + CancellationToken cancellationToken + ) { if (!this.IsAvailable()) { @@ -127,7 +135,7 @@ public Task OnGuildWarsStarting(GuildWarsStartingContext guildWarsStartingContex title: "Wine not installed", description: "Wine is required to launch Guild Wars on Linux. Please install Wine and restart Daybreak.", expirationTime: DateTime.UtcNow + TimeSpan.FromSeconds(15)); - return Task.CompletedTask; + return; } if (!this.IsInitialized()) @@ -138,10 +146,23 @@ public Task OnGuildWarsStarting(GuildWarsStartingContext guildWarsStartingContex title: "Wine prefix not initialized", description: "The Wine prefix needs to be set up before launching Guild Wars. Click here to initialize it.", expirationTime: DateTime.UtcNow + TimeSpan.FromSeconds(15)); - return Task.CompletedTask; + return; + } + + if (await this.ConfigureComputerName(cancellationToken)) + { + return; } - return Task.CompletedTask; + this.logger.LogError( + "Failed to configure the Linux host name as the Wine computer name. Blocking Guild Wars startup" + ); + guildWarsStartingContext.CancelStartup = true; + this.notificationService.NotifyError( + title: "Wine prefix configuration failed", + description: "Daybreak could not configure the Linux host name as the Wine computer name. Check the logs for details.", + expirationTime: DateTime.UtcNow + TimeSpan.FromSeconds(15) + ); } /// @@ -243,6 +264,59 @@ public string GetWinePrefixPath() return this.winePrefixPath; } + public async Task ConfigureComputerName(CancellationToken cancellationToken) + { + if (!this.IsInitialized()) + { + this.logger.LogWarning( + "Cannot configure computer name before the Wine prefix is initialized" + ); + return false; + } + + var computerName = Environment.MachineName; + var useDnsComputerNameConfigured = await this.AddRegistryValue( + WineNetworkRegistryKey, + "UseDnsComputerName", + "N", + "REG_SZ", + cancellationToken + ); + var computerNameConfigured = await this.AddRegistryValue( + ComputerNameRegistryKey, + "ComputerName", + computerName, + "REG_SZ", + cancellationToken + ); + var activeComputerNameConfigured = await this.AddRegistryValue( + ActiveComputerNameRegistryKey, + "ComputerName", + computerName, + "REG_SZ", + cancellationToken + ); + + if ( + !useDnsComputerNameConfigured + || !computerNameConfigured + || !activeComputerNameConfigured + ) + { + this.logger.LogError( + "Failed to configure Wine computer name {ComputerName} from the Linux host name", + computerName + ); + return false; + } + + this.logger.LogInformation( + "Configured Wine computer name {ComputerName} from the Linux host name", + computerName + ); + return true; + } + public IProgressAsyncOperation Install(CancellationToken cancellationToken) { return ProgressAsyncOperation.Create( @@ -280,6 +354,13 @@ CancellationToken cancellationToken "Wine prefix already initialized at {PrefixPath}", this.winePrefixPath ); + + if (!await this.ConfigureComputerName(cancellationToken)) + { + progress.Report(ProgressFailed); + return false; + } + progress.Report(ProgressAlreadyInitialized); return true; } @@ -333,6 +414,13 @@ CancellationToken cancellationToken this.logger.LogWarning("Failed to set d3d9 DLL override, but continuing..."); } + progress.Report(new ProgressUpdate(0.9, "Configuring computer name")); + if (!await this.ConfigureComputerName(cancellationToken)) + { + progress.Report(ProgressFailed); + return false; + } + this.logger.LogInformation("Wine prefix initialized successfully"); progress.Report(ProgressFinished); return true; @@ -390,6 +478,15 @@ CancellationToken cancellationToken startInfo.Environment["WINEPREFIX"] = this.winePrefixPath; + // Guild Wars outlives this call, so its crash backtrace must go somewhere + // durable rather than into the stderr pipe we stop reading below. + if (WineDebugLog.IsEnabled) + { + WineDebugLog.WriteSessionHeader($"{wineExePath} {wineArgs}"); + WineDebugLog.Apply(startInfo); + this.logger.LogDebug("Wine diagnostics are being appended to {WineDebugLog}", WineDebugLog.LogPath); + } + try { using var process = new Process { StartInfo = startInfo }; diff --git a/Dependencies/GWCA/Include/GWCA/Constants/AgentIDs.h b/Dependencies/GWCA/Include/GWCA/Constants/AgentIDs.h index 7ff54a0a..5ac2f50f 100644 --- a/Dependencies/GWCA/Include/GWCA/Constants/AgentIDs.h +++ b/Dependencies/GWCA/Include/GWCA/Constants/AgentIDs.h @@ -470,26 +470,26 @@ namespace GW { } // namespace Minipet namespace SummoningStone { - constexpr int ImperialCripplingSlash = 9043; - constexpr int ImperialTripleChop = 9044; - constexpr int ImperialBarrage = 9045; - constexpr int ImperialQuiveringBlade = 9046; - constexpr int TenguHundredBlades = 9047; - constexpr int TenguBroadHeadArrow = 9048; - constexpr int TenguPalmStrike = 9049; - constexpr int TenguLifeSheath = 9050; - constexpr int TenguAngchuElementalist = 9051; - constexpr int TenguFeveredDreams = 9052; - constexpr int TenguSpitefulSpirit = 9053; - constexpr int TenguPreservation = 9054; - constexpr int TenguPrimalRage = 9055; - constexpr int TenguGlassArrows = 9056; - constexpr int TenguWayOftheAssassin = 9057; - constexpr int TenguPeaceandHarmony = 9058; - constexpr int TenguSandstorm = 9059; - constexpr int TenguPanic = 9060; - constexpr int TenguAuraOftheLich = 9061; - constexpr int TenguDefiantWasXinrae = 9062; + constexpr int ImperialCripplingSlash = 9268; + constexpr int ImperialTripleChop = 9269; + constexpr int ImperialBarrage = 9270; + constexpr int ImperialQuiveringBlade = 9271; + constexpr int TenguHundredBlades = 9272; + constexpr int TenguBroadHeadArrow = 9273; + constexpr int TenguPalmStrike = 9274; + constexpr int TenguLifeSheath = 9275; + constexpr int TenguAngchuElementalist = 9276; + constexpr int TenguFeveredDreams = 9277; + constexpr int TenguSpitefulSpirit = 9278; + constexpr int TenguPreservation = 9279; + constexpr int TenguPrimalRage = 9280; + constexpr int TenguGlassArrows = 9281; + constexpr int TenguWayOftheAssassin = 9282; + constexpr int TenguPeaceandHarmony = 9283; + constexpr int TenguSandstorm = 9284; + constexpr int TenguPanic = 9285; + constexpr int TenguAuraOftheLich = 9286; + constexpr int TenguDefiantWasXinrae = 9287; } // namespace SummoningStone } // namespace ModelID } // namespace Constants diff --git a/Dependencies/GWCA/Include/GWCA/Constants/Constants.h b/Dependencies/GWCA/Include/GWCA/Constants/Constants.h index a885b419..b06a4c07 100644 --- a/Dependencies/GWCA/Include/GWCA/Constants/Constants.h +++ b/Dependencies/GWCA/Include/GWCA/Constants/Constants.h @@ -22,7 +22,7 @@ namespace GW { None, Warrior, Ranger, Monk, Necromancer, Mesmer, Elementalist, Assassin, Ritualist, Paragon, Dervish }; - static const char* GetProfessionAcronym(Profession prof) { + inline const char* GetProfessionAcronym(Profession prof) { switch (prof) { case GW::Constants::Profession::None: return "X"; case GW::Constants::Profession::Warrior: return "W"; @@ -38,7 +38,7 @@ namespace GW { default: return ""; } } - static const wchar_t* GetWProfessionAcronym(Profession prof) { + inline const wchar_t* GetWProfessionAcronym(Profession prof) { switch (prof) { case GW::Constants::Profession::None: return L"X"; case GW::Constants::Profession::Warrior: return L"W"; diff --git a/Dependencies/GWCA/Include/GWCA/Constants/Skills.h b/Dependencies/GWCA/Include/GWCA/Constants/Skills.h index 550308dd..01e78761 100644 --- a/Dependencies/GWCA/Include/GWCA/Constants/Skills.h +++ b/Dependencies/GWCA/Include/GWCA/Constants/Skills.h @@ -3033,9 +3033,8 @@ namespace GW { Vow_of_Revolution, Heroic_Refrain, Reforged_Mode=0xD6A, - Dhuums_Covenant_Broken, - Count = 0xD6c - }; + Dhuums_Covenant_Broken + }; enum class SkillType { Bounty = 1, diff --git a/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h b/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h index 3b47cb10..451f962f 100644 --- a/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h +++ b/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h @@ -560,41 +560,42 @@ namespace GW { kInventoryRelated2, // 0x100001aa, added to GW 2026-02-26 kInventoryRelated3, // 0x100001ab, added to GW 2026-02-26 kInventoryRelated4, // 0x100001ac, added to GW 2026-04-28 - kEquipItem, // 0x100001ad, wparam = { item_id, agent_id } - kMoveItem, // 0x100001ae, wparam = { item_id, to_bag, to_slot, bool prompt } - kItemRelated_1, // 0x100001af - kItemTooltip, // 0x100001b0 - kItemRelated_3, // 0x100001b1, added to GW 2026-02-26 - kItemRelated_4, // 0x100001b2, added to GW 2026-02-26 - kItemRelated_5, // 0x100001b3, added to GW 2026-04-28 - kInitiateTrade, // 0x100001b4 - kMessage_0x100001a7, // 0x100001b5 - kMessage_0x100001a8, // 0x100001b6 - kMessage_0x100001a9, // 0x100001b7 - kMessage_0x100001aa, // 0x100001b8 - kPartySearchWindowDestroyed, // 0x100001b9 - kMessage_0x100001ac, // 0x100001ba - kPartySearchWindowCreated, // 0x100001bb - kMessage_0x100001ae, // 0x100001bc - kMessage_0x100001af, // 0x100001bd - kMessage_0x100001b0, // 0x100001be - kMessage_0x100001b1, // 0x100001bf - kMessage_0x100001b2, // 0x100001c0 - kMessage_0x100001b3, // 0x100001c1 - kMessage_0x100001b4, // 0x100001c2 - kMessage_0x100001b5, // 0x100001c3 - kInventoryAgentChanged, // 0x100001c4, Triggered when inventory needs updating due to agent change; no args - kInventoryRelated_1, // 0x100001c5 - kInventoryRelated_2, // 0x100001c6 - kMissionStatusRelated, // 0x100001c7 - kUnused_1c2, // 0x100001c8 - kCollapseExpandSkillListSection, // 0x100001c9 - kPromptLoadTemplate, // 0x100001ca - kOpenTemplateManager, // 0x100001cb - kPromptSaveTemplate, // 0x100001cc - kOpenTemplate, // 0x100001cd, wparam = GW::UI::ChatTemplate* - kTemplateRelated_3, // 0x100001ce - kTemplateRelated_4, // 0x100001cf + kInventoryRelated4_1, // 0x100001ad, added to GW 2026-08-07 + kEquipItem, // 0x100001ae, wparam = { item_id, agent_id } + kMoveItem, // 0x100001af, wparam = { item_id, to_bag, to_slot, bool prompt } + kItemRelated_1, // 0x100001b0 + kItemTooltip, // 0x100001b1 + kItemRelated_3, // 0x100001b2, added to GW 2026-02-26 + kItemRelated_4, // 0x100001b3, added to GW 2026-02-26 + kItemRelated_5, // 0x100001b4, added to GW 2026-04-28 + kInitiateTrade, // 0x100001b5 + kMessage_0x100001a7, // 0x100001b6 + kMessage_0x100001a8, // 0x100001b7 + kMessage_0x100001a9, // 0x100001b8 + kMessage_0x100001aa, // 0x100001b9 + kPartySearchWindowDestroyed, // 0x100001ba + kMessage_0x100001ac, // 0x100001bb + kPartySearchWindowCreated, // 0x100001bc + kMessage_0x100001ae, // 0x100001bd + kMessage_0x100001af, // 0x100001be + kMessage_0x100001b0, // 0x100001bf + kMessage_0x100001b1, // 0x100001c0 + kMessage_0x100001b2, // 0x100001c1 + kMessage_0x100001b3, // 0x100001c2 + kMessage_0x100001b4, // 0x100001c3 + kMessage_0x100001b5, // 0x100001c4 + kInventoryAgentChanged, // 0x100001c5, Triggered when inventory needs updating due to agent change; no args + kInventoryRelated_1, // 0x100001c6 + kInventoryRelated_2, // 0x100001c7 + kMissionStatusRelated, // 0x100001c8 + kUnused_1c2, // 0x100001c9 + kCollapseExpandSkillListSection, // 0x100001ca + kPromptLoadTemplate, // 0x100001cb + kOpenTemplateManager, // 0x100001cc + kPromptSaveTemplate, // 0x100001cd + kOpenTemplate, // 0x100001ce, wparam = GW::UI::ChatTemplate* + kTemplateRelated_3, // 0x100001cf + kTemplateRelated_4, // 0x100001d0 // GWCA Client to Server commands. Only added the ones that are used for hooks, everything else goes straight into GW @@ -603,7 +604,7 @@ namespace GW { kSendMoveItem = 0x30000000 | 0x5, // 0x30000005, wparam = UIPacket::kSendMoveItem* kSendMerchantRequestQuote = 0x30000000 | 0x6, // 0x30000006, wparam = UIPacket::kSendMerchantRequestQuote* kSendMerchantTransactItem = 0x30000000 | 0x7, // 0x30000007, wparam = UIPacket::kSendMerchantTransactItem* - kSendUseItem = 0x30000000 | 0x8, // 0x30000008, wparam = UIPacket::kSendUseItem* + kSendUseItem = 0x30000000 | 0x8, // 0x30000008, wparam = uint32_t item_id kSendSetActiveQuest = 0x30000000 | 0x9, // 0x30000009, wparam = uint32_t quest_id kSendAbandonQuest = 0x30000000 | 0xA, // 0x3000000a, wparam = uint32_t quest_id kSendChangeTarget = 0x30000000 | 0xB, // 0x3000000b, wparam = UIPacket::kSendChangeTarget* // e.g. tell the gw client to focus on a different target @@ -626,7 +627,7 @@ namespace GW { kChatLinkClicked = 0x30000000 | 0x25 // 0x30000025, wparam = UIPacket::kChatLinkClicked. Triggered when the player clicks an link in chat, e.g. build code }; - //static_assert(GW::UI::UIMessage::kOpenTemplate == (GW::UI::UIMessage)0x100001c4); + static_assert(GW::UI::UIMessage::kOpenTemplate == (GW::UI::UIMessage)0x100001ce); namespace UIPacket { struct kUIFeatureChanged { @@ -723,19 +724,34 @@ namespace GW { uint32_t h0004; uint32_t h0008; }; + // A handler that leaves both entries at 0 falls through to the engine's + // own default sizing (GetMinSize()-based) rather than being treated as "wants zero size". + // + // flags (offset 0xc) is passed through uninitialized in that call site and never visibly + // set before the dispatch - not confirmed as meaningful; treat as reserved/unused for now. struct kMeasureContent { - float max_width; // Maximum width constraint - float max_height; // Maximum height constraint - float* size_output; // Pointer to output buffer for calculated size - uint32_t flags; // Layout flags (similar to the 0x100 flag we saw) - }; + float max_width; // Available width, after the frame's own margin/padding is already subtracted + float max_height; // Available height, after the frame's own margin/padding is already subtracted + float* size_output; // Points at a 2-float [width, height] buffer the handler must fill + uint32_t flags; // Not confirmed meaningful - see comment above + }; + // Every field here is relative to the frame's own content-origin rect (its content_left/ + // content_bottom/content_right/content_top) - not absolute screen coordinates: + // available_width = content_right - content_left + // available_height = content_top - content_bottom + // local_left = resolved_left - content_left + // local_bottom = resolved_bottom - content_bottom + // local_right = resolved_right - content_left + // local_top = resolved_top - content_bottom + // i.e. "here is how much room you have, and here is where your own just-resolved rect sits + // within it" struct kSetLayout { - float field_0x0; - float field_0x4; - float field_0x8; - float field_0xc; float available_width; float available_height; + float local_left; + float local_bottom; + float local_right; + float local_top; }; struct kSetAgentProfession { AgentID agent_id; @@ -939,10 +955,6 @@ namespace GW { uint32_t gold_recv; Merchant::QuoteInfo recv; }; - struct kSendUseItem { - uint32_t item_id; - uint16_t quantity; // Unused, but would be cool - }; struct kSendChatMessage { wchar_t* message; uint32_t agent_id; diff --git a/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h.bak b/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h.bak new file mode 100644 index 00000000..451f962f --- /dev/null +++ b/Dependencies/GWCA/Include/GWCA/Constants/UIMessages.h.bak @@ -0,0 +1,1027 @@ +namespace GW { + enum class CallTargetType : uint32_t; + enum class WorldActionId : uint32_t; + struct GamePos; + struct Effect; + struct Vec2f; + typedef uint32_t AgentID; + + namespace Merchant { + enum class TransactionType : uint32_t; + } + namespace Constants { + enum class Language; + enum class MapID : uint32_t; + enum class InstanceType; + enum class QuestID : uint32_t; + enum class SkillID : uint32_t; + } + namespace Chat { + enum Channel : int; + typedef uint32_t Color; + } + + namespace Chat { + enum Channel : int; + } + namespace SkillbarMgr { + struct SkillTemplate; + } + namespace UI { + struct WindowPosition; + enum class FlagPreference : uint32_t; + enum class NumberPreference : uint32_t; + enum class StringPreference : uint32_t; + enum class EnumPreference : uint32_t; + enum class UiProfileSetting : uint32_t; + struct CompassPoint; + + enum class UIMessage : uint32_t { + kNone = 0x0, // 0x0 + kFrameMessage_0x1, // 0x1 + kFrameMessage_0x2, // 0x2 + kFrameMessage_0x3, // 0x3 + kFrameMessage_0x4, // 0x4 + kFrameMessage_0x5, // 0x5 + kFrameMessage_0x6, // 0x6 + kFrameMessage_0x7, // 0x7 + kResize, // 0x8 + kInitFrame, // 0x9 + kFrameMessage_0xa, // 0xa + kDestroyFrame, // 0xb + kFrameDisabledChange, // 0xc, wparam = is_enabled + kFrameMessage_0xd, // 0xd + kFrameMessage_0xe, // 0xe + kFrameMessage_0xf, // 0xf + kFrameMessage_0x10, // 0x10 + kFrameMessage_0x11, // 0x11 + kFrameMessage_0x12, // 0x12 + kFrameMessage_0x13, // 0x13 + kFrameMessage_0x14, // 0x14 + kFrameMessage_0x15, // 0x15 + kFrameMessage_0x16, // 0x16 + kFrameMessage_0x17, // 0x17 + kFrameMessage_0x18, // 0x18 + kFrameMessage_0x19, // 0x19 + kFrameMessage_0x1a, // 0x1a + kFrameMessage_0x1b, // 0x1b + kFrameMessage_0x1c, // 0x1c + kFrameMessage_0x1d, // 0x1d + kKeyDown = 0x20, // 0x20, wparam = UIPacket::kKeyAction* - Updated from 0x1e to 0x20 + kSetFocus, // 0x21, wparam = 1 or 0 + kKeyUp, // 0x22, wparam = UIPacket::kKeyAction* + kFrameMessage_0x21, // 0x23 + kMouseClick, // 0x24, wparam = UIPacket::kMouseClick* + kFrameMessage_0x23, // 0x25 + kMouseCoordsClick, // 0x26, wparam = UIPacket::kMouseCoordsClick* + kFrameMessage_0x25, // 0x27 + kMouseUp, // 0x28, wparam = UIPacket::kMouseCoordsClick* + kFrameMessage_0x27, // 0x29 + kFrameMessage_0x28, // 0x2a + kFrameMessage_0x29, // 0x2b + kFrameMessage_0x2a, // 0x2c + kFrameMessage_0x2b, // 0x2d + kToggleButtonDown, // 0x2e + kFrameMessage_0x2d, // 0x2f + kMouseClick2 = 0x31, // 0x31, wparam = UIPacket::kMouseAction* + kMouseAction, // 0x32, wparam = UIPacket::kMouseAction* + kRenderFrame_0x30, // 0x33 + kRenderFrame_0x31 = 0x35, // 0x35 + kFrameVisibilityChanged, // 0x36, wparam = is_visible + kSetLayout, // 0x37 + kMeasureContent, // 0x38 + kFrameMessage_0x35, // 0x39 + kFrameMessage_0x36, // 0x3a + kRefreshContent, // 0x3b + kFrameMessage_0x38, // 0x3c + kFrameMessage_0x39, // 0x3d + kFrameMessage_0x3a, // 0x3e + kFrameMessage_0x3b, // 0x3f + kFrameMessage_0x3c = 0x44, // 0x44 + kFrameMessage_0x3d, // 0x45 + kFrameMessage_0x3e, // 0x46 + kFrameMessage_0x3f, // 0x47 + kFrameMessage_0x40, // 0x48 + kFrameMessage_0x41, // 0x49 + kFrameMessage_0x42, // 0x4a + kRenderFrame_0x43, // 0x4b + kFrameMessage_0x44 = 0x52, // 0x52 + kFrameMessage_0x45, // 0x53 + kFrameMessage_0x46 = 0x56, // 0x56 + kFrameMessage_0x47, // 0x57, Multiple uses depending on frame + kFrameMessage_0x48, // 0x58, Multiple uses depending on frame + kFrameMessage_0x49, // 0x59, Multiple uses depending on frame + kFrameMessage_0x4a, // 0x5a, Multiple uses depending on frame + kFrameMessage_0x4b, // 0x5b, Multiple uses depending on frame + kFrameMessage_0x4c, // 0x5c, Multiple uses depending on frame + kFrameMessage_0x4d, // 0x5d, Multiple uses depending on frame + kFrameMessage_0x4e, // 0x5e, Multiple uses depending on frame + kFrameMessage_0x4f, // 0x5f, Multiple uses depending on frame + kFrameMessage_0x50, // 0x60, Multiple uses depending on frame + kFrameMessage_0x51, // 0x61, Multiple uses depending on frame + kFrameMessage_0x52, // 0x62, Multiple uses depending on frame + kFrameMessage_0x53, // 0x63, Multiple uses depending on frame + kFrameMessage_0x53_a, // 0x64, Added 2026-05-28 + kFrameMessage_0x53_b, // 0x65, Added 2026-05-28 + kFrameMessage_0x54, // 0x66, Multiple uses depending on frame + kFrameMessage_0x55, // 0x67, Multiple uses depending on frame + kFrameMessage_0x56, // 0x68, Multiple uses depending on frame + kFrameMessage_0x57, // 0x69, Multiple uses depending on frame + kFrameMessage_0x58, // 0x6a, Multiple uses depending on frame + kFrameMessage_0x59, // 0x6b, Multiple uses depending on frame + + // High bit messages start at 0x10000000 + kMessage_0x10000000 = 0x10000000, // 0x10000000 + kMessage_0x10000001, // 0x10000001 + kMessage_0x10000002, // 0x10000002 + kMessage_0x10000003, // 0x10000003 + kMessage_0x10000004, // 0x10000004 + kMessage_0x10000005, // 0x10000005 + kMessage_0x10000006, // 0x10000006 + kAgentUpdate, // 0x10000007, wparam = uint32_t agent_id + kAgentDestroy, // 0x10000008, wparam = uint32_t agent_id + kUpdateAgentEffects, // 0x10000009 + kMessage_0x1000000a, // 0x1000000a + kMessage_0x1000000b, // 0x1000000b + kDialogueMessage, // 0x1000000c + kAgentOverheadFloat, // 0x1000000d, // e.g. XP gain wparam = *GW::UI::UIPacket::kAgentOverheadNumber + kMessage_0x1000000e, // 0x1000000e + kAgentOverheadNumber, // 0x1000000f, // e.g. damage, heal wparam = *GW::UI::UIPacket::kAgentOverheadNumber + kMessage_0x10000010, // 0x10000010 + kAgentOverheadText, // 0x10000011, // e.g. block, dodge wparam = *GW::UI::UIPacket::kAgentOverheadNumber + kMessage_0x10000012, // 0x10000012 + kMessage_0x10000013, // 0x10000013 + kMessage_0x10000014, // 0x10000014 + kMessage_0x10000015, // 0x10000015 + kMessage_0x10000016, // 0x10000016 + kAgentSpeechBubble, // 0x10000017 + kMessage_0x10000018, // 0x10000018 + kShowAgentNameTag, // 0x10000019, wparam = AgentNameTagInfo* + kHideAgentNameTag, // 0x1000001a + kSetAgentNameTagAttribs, // 0x1000001b, wparam = AgentNameTagInfo* + kMessage_0x1000001c, // 0x1000001c + kSetAgentProfession, // 0x1000001d, wparam = UIPacket::kSetAgentProfession* + kMessage_0x1000001e, // 0x1000001e + kMessage_0x1000001f, // 0x1000001f + kChangeTarget, // 0x10000020, wparam = UIPacket::kChangeTarget* + kMessage_0x10000021, // 0x10000021 + kMessage_0x10000022, // 0x10000022 + kMessage_0x10000023, // 0x10000023 + kAgentSkillActivated, // 0x10000024, kAgentSkillPacket + kAgentSkillActivatedInstantly, // 0x10000025, kAgentSkillPacket + kAgentSkillCancelled, // 0x10000026, kAgentSkillPacket + kAgentSkillStartedCast, // 0x10000027, wparam = UIPacket::kAgentStartCasting* + kMessage_0x10000028, // 0x10000028 + kShowMapEntryMessage, // 0x10000029, wparam = { wchar_t* title, wchar_t* subtitle } + kSetCurrentPlayerData, // 0x1000002a, fired after setting the worldcontext player name + kMessage_0x1000002b, // 0x1000002b + kMessage_0x1000002c, // 0x1000002c + kMessage_0x1000002d, // 0x1000002d + kMessage_0x1000002e, // 0x1000002e + kMessage_0x1000002f, // 0x1000002f + kMessage_0x10000030, // 0x10000030 + kMessage_0x10000031, // 0x10000031 + kMessage_0x10000032, // 0x10000032 + kMessage_0x10000033, // 0x10000033 + kPostProcessingEffect, // 0x10000034, Triggered when drunk. wparam = UIPacket::kPostProcessingEffect + kMessage_0x10000035, // 0x10000035 + kMessage_0x10000036, // 0x10000036 + kMessage_0x10000037, // 0x10000037 + kHeroAgentAdded, // 0x10000038, hero assigned to agent/inventory/ai mode + kHeroDataAdded, // 0x10000039, hero info received from server (name, level etc) + kMessage_0x1000003a, // 0x1000003a + kMessage_0x1000003b, // 0x1000003b + kMessage_0x1000003c, // 0x1000003c + kMessage_0x1000003d, // 0x1000003d + kMessage_0x1000003e, // 0x1000003e + kMessage_0x1000003f, // 0x1000003f + kShowXunlaiChest, // 0x10000040 + kMessage_0x10000041, // 0x10000041 + kMessage_0x10000042, // 0x10000042 + kMessage_0x10000043, // 0x10000043 + kMessage_0x10000044, // 0x10000044 + kMessage_0x10000045, // 0x10000045 + kMinionCountUpdated, // 0x10000046 + kMoraleChange, // 0x10000047, wparam = {agent id, morale percent } + kMessage_0x10000048, // 0x10000048 + kMessage_0x10000049, // 0x10000049 + kMessage_0x1000004a, // 0x1000004a + kMessage_0x1000004b, // 0x1000004b + kMessage_0x1000004c, // 0x1000004c + kMessage_0x1000004d, // 0x1000004d + kMessage_0x1000004e, // 0x1000004e + kMessage_0x1000004f, // 0x1000004f + kLoginStateChanged, // 0x10000050, wparam = {bool is_logged_in, bool unk } + kMessage_0x10000051, // 0x10000051 + kMessage_0x10000052, // 0x10000052 + kMessage_0x10000053, // 0x10000053 + kMessage_0x10000054, // 0x10000054 + kEffectAdd, // 0x10000055, wparam = {agent_id, GW::Effect*} + kEffectRenew, // 0x10000056, wparam = GW::Effect* + kEffectRemove, // 0x10000057, wparam = effect id + kMessage_0x10000058, // 0x10000058 + kMessage_0x10000059, // 0x10000059 + kMessage_0x1000005a, // 0x1000005a + kSkillActivated, // 0x1000005b, wparam ={ uint32_t agent_id , uint32_t skill_id } + kMessage_0x1000005c, // 0x1000005c + kMessage_0x1000005d, // 0x1000005d + kUpdateSkillbar, // 0x1000005e, wparam ={ uint32_t agent_id , ... } + kUpdateSkillsAvailable, // 0x1000005f, Triggered on a skill unlock, profession change or map load + kMessage_0x10000060, // 0x10000060 + kMessage_0x10000061, // 0x10000061 + kMessage_0x10000062, // 0x10000062 + kMessage_0x10000063, // 0x10000063 + kPlayerTitleChanged, // 0x10000064, wparam = { uint32_t player_id, uint32_t title_id } + kTitleProgressUpdated, // 0x10000065, wparam = title_id + kExperienceGained, // 0x10000066, wparam = experience amount + kMessage_0x10000067, // 0x10000067 + kMessage_0x10000068, // 0x10000068 + kMessage_0x10000069, // 0x10000069 + kMessage_0x1000006a, // 0x1000006a + kMessage_0x1000006b, // 0x1000006b + kMessage_0x1000006c, // 0x1000006c + kMessage_0x1000006d, // 0x1000006d + kMessage_0x1000006e, // 0x1000006e + kMessage_0x1000006f, // 0x1000006f + kMessage_0x10000070, // 0x10000070 + kMessage_0x10000071, // 0x10000071 + kMessage_0x10000072, // 0x10000072 + kMessage_0x10000073, // 0x10000073 + kMessage_0x10000074, // 0x10000074 + kMessage_0x10000075, // 0x10000075 + kMessage_0x10000076, // 0x10000076 + kMessage_0x10000077, // 0x10000077 + kMessage_0x10000078, // 0x10000078 + kMessage_0x10000079, // 0x10000079 + kMessage_0x1000007a, // 0x1000007a + kMessage_0x1000007b, // 0x1000007b + kMessage_0x1000007c, // 0x1000007c + kMessage_0x1000007d, // 0x1000007d + kMessage_0x1000007e, // 0x1000007e + kWriteToChatLog, // 0x1000007f, wparam = UIPacket::kWriteToChatLog* + kWriteToChatLogWithSender, // 0x10000080, wparam = UIPacket::kWriteToChatLogWithSender* + kAllyOrGuildMessage, // 0x10000081, wparam = UIPacket::kAllyOrGuildMessage* + kPlayerChatMessage, // 0x10000082, wparam = UIPacket::kPlayerChatMessage* + kMessage_0x10000083, // 0x10000083 + kFloatingWindowMoved, // 0x10000084, wparam = frame_id + kMessage_0x10000085, // 0x10000085 + kUIFeatureChanged, // 0x10000086, wparam = UIPacket::kUIFeatureChanged* + kMessage_0x10000087, // 0x10000087 + kMessage_0x10000088, // 0x10000088 + kMessage_0x10000089, // 0x10000089 + kMessage_0x1000008a, // 0x1000008a + kFriendUpdated, // 0x1000008b, wparam = { GW::Friend*, ... } + kMapLoaded, // 0x1000008c + kMessage_0x1000008d, // 0x1000008d + kMessage_0x1000008e, // 0x1000008e + kMessage_0x1000008f, // 0x1000008f + kMessage_0x10000090, // 0x10000090 + kMessage_0x10000091, // 0x10000091 + kOpenWhisper, // 0x10000092, wparam = wchar* name + kMessage_0x10000093, // 0x10000093 + kMessage_0x10000094, // 0x10000094 + kMessage_0x10000095, // 0x10000095 + kMessage_0x10000096, // 0x10000096 + kMessage_0x10000097, // 0x10000097 + kLoadMapContext, // 0x10000098, wparam = UIPacket::kLoadMapContext + kMessage_0x10000099, // 0x10000099 + kMessage_0x1000009a, // 0x1000009a + kMessage_0x1000009b, // 0x1000009b + kDialogueMessageUpdated, // 0x1000009c + kLogout, // 0x1000009d, wparam = { bool unknown, bool character_select } + kCompassDraw, // 0x1000009e, wparam = UIPacket::kCompassDraw* + kMessage_0x1000009f, // 0x1000009f + kMessage_0x100000a0, // 0x100000a0 + kMessage_0x100000a1, // 0x100000a1 + kOnScreenMessage, // 0x100000a2, wparam = wchar_** encoded_string + kDialogButton, // 0x100000a3, wparam = DialogButtonInfo* + kMessage_0x100000a4, // 0x100000a4 + kMessage_0x100000a5, // 0x100000a5 + kDialogBody, // 0x100000a6, wparam = DialogBodyInfo* + kMessage_0x100000a7, // 0x100000a7 + kMessage_0x100000a8, // 0x100000a8 + kMessage_0x100000a9, // 0x100000a9 + kMessage_0x100000aa, // 0x100000aa + kMessage_0x100000ab, // 0x100000ab + kMessage_0x100000ac, // 0x100000ac + kMessage_0x100000ad, // 0x100000ad + kMessage_0x100000ae, // 0x100000ae + kMessage_0x100000af, // 0x100000af + kMessage_0x100000b0, // 0x100000b0 + kMessage_0x100000b1, // 0x100000b1 + kMessage_0x100000b2, // 0x100000b2 + kTargetNPCPartyMember, // 0x100000b3, wparam = { uint32_t unk, uint32_t agent_id } + kTargetPlayerPartyMember, // 0x100000b4, wparam = { uint32_t unk, uint32_t player_number } + kVendorWindow, // 0x100000b5, wparam = UIPacket::kVendorWindow + kVendorError, // 0x100000b6, wparam = { uint32_t flag, uint32_t error_id } + kMessage_0x100000b7, // 0x100000b7 + kMessage_0x100000b8, // 0x100000b8 + kVendorItems, // 0x100000b9, wparam = UIPacket::kVendorItems + kMessage_0x100000ba, // 0x100000ba + kVendorTransComplete, // 0x100000bb, wparam = *TransactionType + kMessage_0x100000bc, // 0x100000bc + kVendorQuote, // 0x100000bd, wparam = UIPacket::kVendorQuote + kMessage_0x100000be, // 0x100000be + kMessage_0x100000bf, // 0x100000bf + kMessage_0x100000c0, // 0x100000c0 + kMessage_0x100000c1, // 0x100000c1 + kStartMapLoad, // 0x100000c2, wparam = { uint32_t map_id, ...} + kMessage_0x100000c3, // 0x100000c3 + kMessage_0x100000c4, // 0x100000c4 + kMessage_0x100000c5, // 0x100000c5 + kMessage_0x100000c6, // 0x100000c6 + kWorldMapUpdated, // 0x100000c7, Triggered when an area in the world map has been discovered/updated + kMessage_0x100000c8, // 0x100000c8 + kMessage_0x100000c9, // 0x100000c9 + kMessage_0x100000ca, // 0x100000ca + kMessage_0x100000cb, // 0x100000cb + kMessage_0x100000cc, // 0x100000cc + kMessage_0x100000cd, // 0x100000cd + kMessage_0x100000ce, // 0x100000ce + kMessage_0x100000cf, // 0x100000cf + kMessage_0x100000d0, // 0x100000d0 + kMessage_0x100000d1, // 0x100000d1 + kMessage_0x100000d2, // 0x100000d2 + kMessage_0x100000d3, // 0x100000d3 + kMessage_0x100000d4, // 0x100000d4 + kMessage_0x100000d5, // 0x100000d5 + kMessage_0x100000d6, // 0x100000d6 + kMessage_0x100000d7, // 0x100000d7 + kMessage_0x100000d8, // 0x100000d8 + kMessage_0x100000d9, // 0x100000d9 + kGuildMemberUpdated, // 0x100000da, wparam = { GuildPlayer::name_ptr } + kMessage_0x100000db, // 0x100000db + kMessage_0x100000dc, // 0x100000dc + kMessage_0x100000dd, // 0x100000dd + kMessage_0x100000de, // 0x100000de + kMessage_0x100000df, // 0x100000df + kMessage_0x100000e0, // 0x100000e0 + kShowHint, // 0x100000e1, wparam = { uint32_t icon_type, wchar_t* message_enc } + kMessage_0x100000e2, // 0x100000e2 + kMessage_0x100000e3, // 0x100000e3 + kMessage_0x100000e4, // 0x100000e4 + kMessage_0x100000e5, // 0x100000e5 + kMessage_0x100000e6, // 0x100000e6 + kMessage_0x100000e7, // 0x100000e7 + kMessage_0x100000e8, // 0x100000e8 + kWeaponSetSwapComplete, // 0x100000e9, wparam = UIPacket::kWeaponSwap* + kWeaponSetSwapCancel, // 0x100000ea + kWeaponSetUpdated, // 0x100000eb + kUpdateGoldCharacter, // 0x100000ec, wparam = { uint32_t unk, uint32_t gold_character } + kUpdateGoldStorage, // 0x100000ed, wparam = { uint32_t unk, uint32_t gold_storage } + kInventorySlotUpdated, // 0x100000ee, Triggered when an item is moved into a slot + kEquipmentSlotUpdated, // 0x100000ef, Triggered when an item is moved into a slot + kMessage_0x100000f0, // 0x100000f0 + kInventorySlotCleared, // 0x100000f1, Triggered when an item has been removed from a slot + kEquipmentSlotCleared, // 0x100000f2, Triggered when an item has been removed from a slot + kMessage_0x100000f3, // 0x100000f3 + kMessage_0x100000f4, // 0x100000f4 + kMessage_0x100000f5, // 0x100000f5 + kMessage_0x100000f6, // 0x100000f6 + kMessage_0x100000f7, // 0x100000f7 + kMessage_0x100000f8, // 0x100000f8 + kMessage_0x100000f9, // 0x100000f9 + kPvPWindowContent, // 0x100000fa + kMessage_0x100000fb, // 0x100000fb + kMessage_0x100000fc, // 0x100000fc + kMessage_0x100000fd, // 0x100000fd + kMessage_0x100000fe, // 0x100000fe + kMessage_0x100000ff, // 0x100000ff + kMessage_0x10000100, // 0x10000100 + kMessage_0x10000101, // 0x10000101 + kPreStartSalvage, // 0x10000102, { uint32_t item_id, uint32_t kit_id } + kTomeSkillSelection, // 0x10000103, wparam = UIPacket::kTomeSkillSelection* + kMessage_0x10000104, // 0x10000104 + kTradePlayerUpdated, // 0x10000105, wparam = GW::TraderPlayer* + kItemUpdated, // 0x10000106, wparam = UIPacket::kItemUpdated* + kMessage_0x10000107, // 0x10000107 + kMessage_0x10000108, // 0x10000108 + kMessage_0x10000109, // 0x10000109 + kMessage_0x1000010a, // 0x1000010a + kMessage_0x1000010b, // 0x1000010b + kMessage_0x1000010c, // 0x1000010c + kMessage_0x1000010d, // 0x1000010d + kMessage_0x1000010e, // 0x1000010e + kMessage_0x1000010f, // 0x1000010f + kMessage_0x10000110, // 0x10000110 + kMapChange, // 0x10000111, wparam = map id + kMessage_0x10000112, // 0x10000112 + kMessage_0x10000113, // 0x10000113 + kMessage_0x10000114, // 0x10000114 + kCalledTargetChange, // 0x10000115, wparam = { player_number, target_id } + kMessage_0x10000116, // 0x10000116 + kMessage_0x10000117, // 0x10000117 + kMessage_0x10000118, // 0x10000118 + kErrorMessage, // 0x10000119, wparam = { int error_index, wchar_t* error_encoded_string } + kPartyHardModeChanged, // 0x1000011a, wparam = { int is_hard_mode } + kPartyAddHenchman, // 0x1000011b + kPartyRemoveHenchman, // 0x1000011c + kMessage_0x1000011d, // 0x1000011d + kPartyAddHero, // 0x1000011e + kPartyRemoveHero, // 0x1000011f + kMessage_0x10000120, // 0x10000120 + kMessage_0x10000121, // 0x10000121 + kMessage_0x10000122, // 0x10000122 + kMessage_0x10000123, // 0x10000123 + kPartyAddPlayer, // 0x10000124 + kMessage_0x10000125, // 0x10000125 + kPartyRemovePlayer, // 0x10000126 + kMessage_0x10000127, // 0x10000127 + kMessage_0x10000128, // 0x10000128 + kMessage_0x10000129, // 0x10000129 + kDisableEnterMissionBtn, // 0x1000012a, wparam = boolean (1 = disabled, 0 = enabled) + kMessage_0x1000012b, // 0x1000012b + kMessage_0x1000012c, // 0x1000012c + kShowCancelEnterMissionBtn, // 0x1000012d + kMessage_0x1000012e, // 0x1000012e + kPartyDefeated, // 0x1000012f + kMessage_0x10000130, // 0x10000130 + kMessage_0x10000131, // 0x10000131 + kMessage_0x10000132, // 0x10000132 + kPartySearchCreated, // 0x10000133, wparam = GW::PartySearch* + kPartySearchIdChanged, // 0x10000134, wparam = uint32_t* party_search_id + kPartySearchRemoved, // 0x10000135, wparam = uint32_t* party_search_id + kPartySearchUpdated, // 0x10000136, wparam = GW::PartySearch* + kPartySearchInviteReceived, // 0x10000137, wparam = UIPacket::kPartySearchInviteReceived* + kMessage_0x10000138, // 0x10000138 + kPartySearchInviteSent, // 0x10000139 + kPartyShowConfirmDialog, // 0x1000013a, wparam = UIPacket::kPartyShowConfirmDialog + kMessage_0x1000013b, // 0x1000013b + kMessage_0x1000013c, // 0x1000013c + kMessage_0x1000013d, // 0x1000013d + kMessage_0x1000013e, // 0x1000013e + kMessage_0x1000013f, // 0x1000013f + kPreferenceEnumChanged, // 0x10000140, wparam = UiPacket::kPreferenceEnumChanged + kPreferenceFlagChanged, // 0x10000141, wparam = UiPacket::kPreferenceFlagChanged + kPreferenceValueChanged, // 0x10000142, wparam = UiPacket::kPreferenceValueChanged + kUIPositionChanged, // 0x10000143, wparam = UIPacket::kUIPositionChanged + kPreBuildLoginScene, // 0x10000144, Called with no args right before login scene is drawn + kMessage_0x10000145, // 0x10000145 + kMessage_0x10000146, // 0x10000146 + kMessage_0x10000147, // 0x10000147 + kMessage_0x10000148, // 0x10000148 + kMessage_0x10000149, // 0x10000149 + kMessage_0x1000014a, // 0x1000014a + kMessage_0x1000014b, // 0x1000014b + kMessage_0x1000014c, // 0x1000014c + kMessage_0x1000014d, // 0x1000014d + kQuestAdded, // 0x1000014e, wparam = { quest_id, ... } + kQuestDetailsChanged, // 0x1000014f, wparam = { quest_id, ... } + kQuestRemoved, // 0x10000150, wparam = { quest_id, ... } + kClientActiveQuestChanged, // 0x10000151, wparam = { quest_id, ... }. Triggered when the game requests the current quest to change + kMessage_0x10000152, // 0x10000152 + kServerActiveQuestChanged, // 0x10000153, wparam = UIPacket::kServerActiveQuestChanged*. Triggered when the server requests the current quest to change + kUnknownQuestRelated, // 0x10000154 + kMessage_0x10000155, // 0x10000155 + kDungeonComplete, // 0x10000156 + kMissionComplete, // 0x10000157 + kMessage_0x10000158, // 0x10000158 + kVanquishComplete, // 0x10000159 + kObjectiveAdd, // 0x1000015a, wparam = UIPacket::kObjectiveAdd* + kObjectiveComplete, // 0x1000015b, wparam = UIPacket::kObjectiveComplete* + kObjectiveUpdated, // 0x1000015c, wparam = UIPacket::kObjectiveUpdated* + kMessage_0x1000015d, // 0x1000015d + kMessage_0x1000015e, // 0x1000015e + kMessage_0x1000015f, // 0x1000015f + kMessage_0x10000160, // 0x10000160 + kMessage_0x10000161, // 0x10000161 + kMessage_0x10000162, // 0x10000162 + kMessage_0x10000163, // 0x10000163 + kMessage_0x10000164, // 0x10000164 + kTradeSessionStart, // 0x10000165, wparam = { trade_state, player_number } + kMessage_0x10000166, // 0x10000166 + kMessage_0x10000167, // 0x10000167 + kMessage_0x10000168, // 0x10000168 + kMessage_0x10000169, // 0x10000169 + kMessage_0x1000016a, // 0x1000016a + kTradeSessionUpdated, // 0x1000016b, no args + kMessage_0x1000016c, // 0x1000016c + kMessage_0x1000016d, // 0x1000016d + kMessage_0x1000016e, // 0x1000016e + kMessage_0x1000016e_1, // 0x1000016f, added to GW 2026-04-28 + kMessage_0x1000016f, // 0x10000170 + kMessage_0x10000170, // 0x10000171 + kMessage_0x10000171, // 0x10000172 + kMessage_0x10000172, // 0x10000173 + kMessage_0x10000173, // 0x10000174 + kMessage_0x10000174, // 0x10000175 + kCheckUIState, // 0x10000176 + kMessage_0x10000176, // 0x10000177 + kMessage_0x10000177, // 0x10000178 + kMessage_0x10000178, // 0x10000179 + kMessage_0x10000178_1, // 0x1000017a, added to GW 2026-02-26 + kMessage_0x10000178_2, // 0x1000017b, added to GW 2026-02-26 + kMessage_0x10000178_3, // 0x1000017c, added to GW 2026-02-26 + kDestroyUIPositionOverlay, // 0x1000017d + kEnableUIPositionOverlay, // 0x1000017e, wparam = uint32_t enable + kMessage_0x1000017b, // 0x1000017f + kGuildHall, // 0x10000180, wparam = gh key (uint32_t[4]) + kMessage_0x1000017d, // 0x10000181 + kLeaveGuildHall, // 0x10000182 + kTravel, // 0x10000183 + kOpenWikiUrl, // 0x10000184, wparam = char* url + kMessage_0x10000181, // 0x10000185 + kOpenUrlNoPrompt, // 0x10000186, wparam = char* url + kSetPreGameContext_Value0, // 0x10000187, wparam = uint32_t value + kMessage_0x10000184, // 0x10000188 + kGetPreGameContext_Value0, // 0x10000189, lparam = *uint32_t value_out + kSetPreGameContext_Value1, // 0x1000018a, wparam = uint32_t value , added to GW 2026-02-06 + kGetPreGameContext_Value1, // 0x1000018b, lparam = *uint32_t value_out, added to GW 2026-02-06 + kMessage_0x10000186, // 0x1000018c + kMessage_0x10000187, // 0x1000018d + kMessage_0x10000188, // 0x1000018e + kMessage_0x10000189, // 0x1000018f + kMessage_0x1000018a, // 0x10000190 + kMessage_0x1000018b, // 0x10000191 + kMessage_0x1000018c, // 0x10000192 + kMessage_0x1000018d, // 0x10000193 + kMessage_0x1000018d_2, // 0x10000194, added to GW 2026-06-17 + kAppendMessageToChat, // 0x10000195, wparam = wchar_t* message + kMessage_0x1000018f, // 0x10000196 + kMessage_0x10000190, // 0x10000197 + kMessage_0x10000191, // 0x10000198 + kMessage_0x10000192, // 0x10000199 + kMessage_0x10000193, // 0x1000019a + kMessage_0x10000194, // 0x1000019b + kMessage_0x10000195, // 0x1000019c + kMessage_0x10000196, // 0x1000019d + kMessage_0x10000197, // 0x1000019e + kMessage_0x10000198, // 0x1000019f + kMessage_0x10000199, // 0x100001a0 + kMessage_0x1000019a, // 0x100001a1 + kMessage_0x1000019b, // 0x100001a2 + kHideHeroPanel, // 0x100001a3, wparam = hero_id + kShowHeroPanel, // 0x100001a4, wparam = hero_id + kMessage_0x1000019e, // 0x100001a5 + kMessage_0x1000019f, // 0x100001a6 + kQuerySuppressedKeyAction, // 0x100001a7, wparam = 0, lparam = bool*. Used to check if the suppress action is pressed (usually shift) + kGetInventoryAgentId, // 0x100001a8, wparam = 0, lparam = uint32_t* agent_id_out. Used to fetch which agent is selected + kInventoryRelated1, // 0x100001a9, added to GW 2026-02-26 + kInventoryRelated2, // 0x100001aa, added to GW 2026-02-26 + kInventoryRelated3, // 0x100001ab, added to GW 2026-02-26 + kInventoryRelated4, // 0x100001ac, added to GW 2026-04-28 + kInventoryRelated4_1, // 0x100001ad, added to GW 2026-08-07 + kEquipItem, // 0x100001ae, wparam = { item_id, agent_id } + kMoveItem, // 0x100001af, wparam = { item_id, to_bag, to_slot, bool prompt } + kItemRelated_1, // 0x100001b0 + kItemTooltip, // 0x100001b1 + kItemRelated_3, // 0x100001b2, added to GW 2026-02-26 + kItemRelated_4, // 0x100001b3, added to GW 2026-02-26 + kItemRelated_5, // 0x100001b4, added to GW 2026-04-28 + kInitiateTrade, // 0x100001b5 + kMessage_0x100001a7, // 0x100001b6 + kMessage_0x100001a8, // 0x100001b7 + kMessage_0x100001a9, // 0x100001b8 + kMessage_0x100001aa, // 0x100001b9 + kPartySearchWindowDestroyed, // 0x100001ba + kMessage_0x100001ac, // 0x100001bb + kPartySearchWindowCreated, // 0x100001bc + kMessage_0x100001ae, // 0x100001bd + kMessage_0x100001af, // 0x100001be + kMessage_0x100001b0, // 0x100001bf + kMessage_0x100001b1, // 0x100001c0 + kMessage_0x100001b2, // 0x100001c1 + kMessage_0x100001b3, // 0x100001c2 + kMessage_0x100001b4, // 0x100001c3 + kMessage_0x100001b5, // 0x100001c4 + kInventoryAgentChanged, // 0x100001c5, Triggered when inventory needs updating due to agent change; no args + kInventoryRelated_1, // 0x100001c6 + kInventoryRelated_2, // 0x100001c7 + kMissionStatusRelated, // 0x100001c8 + kUnused_1c2, // 0x100001c9 + kCollapseExpandSkillListSection, // 0x100001ca + kPromptLoadTemplate, // 0x100001cb + kOpenTemplateManager, // 0x100001cc + kPromptSaveTemplate, // 0x100001cd + kOpenTemplate, // 0x100001ce, wparam = GW::UI::ChatTemplate* + kTemplateRelated_3, // 0x100001cf + kTemplateRelated_4, // 0x100001d0 + + // GWCA Client to Server commands. Only added the ones that are used for hooks, everything else goes straight into GW + + kSendLoadSkillTemplate = 0x30000000 | 0x3, // 0x30000003, wparam = SkillbarMgr::SkillTemplate* + kSendPingWeaponSet = 0x30000000 | 0x4, // 0x30000004, wparam = UIPacket::kSendPingWeaponSet* + kSendMoveItem = 0x30000000 | 0x5, // 0x30000005, wparam = UIPacket::kSendMoveItem* + kSendMerchantRequestQuote = 0x30000000 | 0x6, // 0x30000006, wparam = UIPacket::kSendMerchantRequestQuote* + kSendMerchantTransactItem = 0x30000000 | 0x7, // 0x30000007, wparam = UIPacket::kSendMerchantTransactItem* + kSendUseItem = 0x30000000 | 0x8, // 0x30000008, wparam = uint32_t item_id + kSendSetActiveQuest = 0x30000000 | 0x9, // 0x30000009, wparam = uint32_t quest_id + kSendAbandonQuest = 0x30000000 | 0xA, // 0x3000000a, wparam = uint32_t quest_id + kSendChangeTarget = 0x30000000 | 0xB, // 0x3000000b, wparam = UIPacket::kSendChangeTarget* // e.g. tell the gw client to focus on a different target + kSendCallTarget = 0x30000000 | 0x13, // 0x30000013, wparam = { uint32_t call_type, uint32_t agent_id } // also used to broadcast morale, death penalty, "I'm following X", etc + kSendDialog = 0x30000000 | 0x16, // 0x30000016, wparam = dialog_id // internal use + + + kStartWhisper = 0x30000000 | 0x17, // 0x30000017, wparam = UIPacket::kStartWhisper* + kGetSenderColor = 0x30000000 | 0x18, // 0x30000018, wparam = UIPacket::kGetColor* // Get chat sender color depending on channel, output object passed by reference + kGetMessageColor = 0x30000000 | 0x19, // 0x30000019, wparam = UIPacket::kGetColor* // Get chat message color depending on channel, output object passed by reference + kSendChatMessage = 0x30000000 | 0x1B, // 0x3000001b, wparam = UIPacket::kSendChatMessage* + kLogChatMessage = 0x30000000 | 0x1D, // 0x3000001d, wparam = UIPacket::kLogChatMessage*. Triggered when a message wants to be added to the persistent chat log. + kRecvWhisper = 0x30000000 | 0x1E, // 0x3000001e, wparam = UIPacket::kRecvWhisper* + kPrintChatMessage = 0x30000000 | 0x1F, // 0x3000001f, wparam = UIPacket::kPrintChatMessage*. Triggered when a message wants to be added to the in-game chat window. + kSendWorldAction = 0x30000000 | 0x20, // 0x30000020, wparam = UIPacket::kSendWorldAction* + kSetRendererValue = 0x30000000 | 0x21, // 0x30000021, wparam = UIPacket::kSetRendererValue + kIdentifyItem = 0x30000000 | 0x22, // 0x30000022, wparam = UIPacket::kUseKitOnItem + kSalvageItem = 0x30000000 | 0x23, // 0x30000023, wparam = UIPacket::kUseKitOnItem + kAddCustomChatLink = 0x30000000 | 0x24, // 0x30000024, wparam = UIPacket::kAddCustomChatLink. Triggered when GWCA parses a [label;url] link in chat that GW didn't handle + kChatLinkClicked = 0x30000000 | 0x25 // 0x30000025, wparam = UIPacket::kChatLinkClicked. Triggered when the player clicks an link in chat, e.g. build code + }; + + static_assert(GW::UI::UIMessage::kOpenTemplate == (GW::UI::UIMessage)0x100001ce); + + namespace UIPacket { + struct kUIFeatureChanged { + GW::UI::UiProfileSetting setting; + bool flag; + }; + struct kAddCustomChatLink { + GW::Chat::Channel channel; // GW::Chat::Channel + wchar_t link_prefix[32]; + wchar_t sender[32]; // whoever sent the chat message + wchar_t label[256]; + wchar_t url[256]; + }; + struct kChatLinkClicked { + const wchar_t* sender; + const wchar_t* label; + const wchar_t* url; + }; + struct kAgentOverheadNumber { + uint32_t agent_id; + uint32_t h0004; + wchar_t* encoded_str; + uint32_t style; + uint32_t h0010; + uint32_t h0014; + uint32_t h0018; + uint32_t h001c; + uint32_t agent_id_dupe; + int amount; + }; + static_assert(sizeof(kAgentOverheadNumber) == 40); + struct kDialogueMessage { + uint32_t agent_id; + wchar_t* sender; + wchar_t* message; + uint32_t duration; + uint32_t is_dialogue_1_or_2; + }; + struct kErrorMessage { + uint32_t error_id; + wchar_t* message; + }; + struct kAgentSkillPacket { + uint32_t agent_id; + GW::Constants::SkillID skill_id; + }; + struct kLoadMapContext { + const wchar_t* file_name; + Constants::MapID map_id; + Constants::InstanceType map_type; + Vec2f spawn_point; + uint32_t h0014; + uint32_t h0018; + bool* success; + }; + static_assert(sizeof(kLoadMapContext) == 0x20); + struct kMouseCoordsClick { + float offset_x; + float offset_y; + uint32_t h0008; + uint32_t h000c; + uint32_t* h0010; + uint32_t h0014; + }; + struct kUseKitOnItem { + uint32_t item_id; + uint32_t kit_id; + }; + struct kShowXunlaiChest { + uint32_t h0000 = 0; + bool storage_pane_unlocked = true; + bool anniversary_pane_unlocked = true; + }; + struct kMoveItem { + uint32_t item_id; + uint32_t to_bag_index; + uint32_t to_slot; + uint32_t prompt; + }; + struct kResize { + uint32_t h0000 = 0xffffffff; + float content_width; + float content_height; + float h000c = 0; + float h0010 = 0; + float content_width2; + float content_height2; + float margin_x; + float margin_y; + // ... + }; + struct kTomeSkillSelection { + uint32_t item_id; + uint32_t h0004; + uint32_t h0008; + }; + // A handler that leaves both entries at 0 falls through to the engine's + // own default sizing (GetMinSize()-based) rather than being treated as "wants zero size". + // + // flags (offset 0xc) is passed through uninitialized in that call site and never visibly + // set before the dispatch - not confirmed as meaningful; treat as reserved/unused for now. + struct kMeasureContent { + float max_width; // Available width, after the frame's own margin/padding is already subtracted + float max_height; // Available height, after the frame's own margin/padding is already subtracted + float* size_output; // Points at a 2-float [width, height] buffer the handler must fill + uint32_t flags; // Not confirmed meaningful - see comment above + }; + // Every field here is relative to the frame's own content-origin rect (its content_left/ + // content_bottom/content_right/content_top) - not absolute screen coordinates: + // available_width = content_right - content_left + // available_height = content_top - content_bottom + // local_left = resolved_left - content_left + // local_bottom = resolved_bottom - content_bottom + // local_right = resolved_right - content_left + // local_top = resolved_top - content_bottom + // i.e. "here is how much room you have, and here is where your own just-resolved rect sits + // within it" + struct kSetLayout { + float available_width; + float available_height; + float local_left; + float local_bottom; + float local_right; + float local_top; + }; + struct kSetAgentProfession { + AgentID agent_id; + uint32_t primary; + uint32_t secondary; + }; + struct kWeaponSwap { + uint32_t weapon_bar_frame_id; + uint32_t weapon_set_id; + }; + struct kWeaponSetChanged { + uint32_t h0000; + uint32_t h0004; + uint32_t h0008; + uint32_t h000c; + }; + struct kChangeTarget { + uint32_t evaluated_target_id; + bool has_evaluated_target_changed; + uint32_t auto_target_id; + bool has_auto_target_changed; + uint32_t manual_target_id; + bool has_manual_target_changed; + }; + struct kSendLoadSkillTemplate { + uint32_t agent_id; + SkillbarMgr::SkillTemplate* skill_template; + }; + struct kVendorWindow { + Merchant::TransactionType transaction_type; + uint32_t unk; + uint32_t merchant_agent_id; + uint32_t is_pending; + }; + struct kVendorQuote { + uint32_t item_id; + uint32_t price; + }; + struct kVendorItems { + Merchant::TransactionType transaction_type; + uint32_t item_ids_count; + uint32_t* item_ids_buffer1; // world->merchant_items.buffer + uint32_t* item_ids_buffer2; // world->merchant_items2.buffer + }; + struct kSetRendererValue { + uint32_t renderer_mode; // 0 for window, 2 for full screen + uint32_t metric_id; // TODO: Enum this! + uint32_t value; + }; + struct kEffectAdd { + uint32_t agent_id; + Effect* effect; + }; + struct kAgentSpeechBubble { + uint32_t agent_id; + wchar_t* message; + uint32_t h0008; + uint32_t h000c; + }; + struct kAgentSkillStartedCast { + uint32_t agent_id; + Constants::SkillID skill_id; + float duration; + uint32_t h000c; + }; + struct kPreStartSalvage { + uint32_t item_id; + uint32_t kit_id; + }; + struct kServerActiveQuestChanged { + GW::Constants::QuestID quest_id; + GW::GamePos marker; + uint32_t h0024; + GW::Constants::MapID map_id; + uint32_t log_state; + }; + struct kPrintChatMessage { + GW::Chat::Channel channel; + wchar_t* message; + FILETIME timestamp; + uint32_t is_reprint; + }; + struct kPartyShowConfirmDialog { + uint32_t ui_message_to_send_to_party_frame; + uint32_t prompt_identitifier; + wchar_t* prompt_enc_str; + }; + struct kUIPositionChanged { + uint32_t window_id; + UI::WindowPosition* position; + }; + struct kPreferenceFlagChanged { + UI::FlagPreference preference_id; + uint32_t new_value; + }; + struct kPreferenceValueChanged { + UI::NumberPreference preference_id; + uint32_t new_value; + }; + struct kPreferenceEnumChanged { + UI::EnumPreference preference_id; + uint32_t enum_index; + }; + struct kPartySearchInvite { + uint32_t source_party_search_id; + uint32_t dest_party_search_id; + }; + struct kPostProcessingEffect { + uint32_t tint; + float amount; + }; + struct kLogout { + uint32_t unknown; + uint32_t character_select; + }; + static_assert(sizeof(kLogout) == 0x8); + + struct kKeyAction { + uint32_t gw_key; + uint32_t modifiers = 0; + uint32_t state_flags = 0; // shift held = 0x4, ctrl = 0x2, alt = 0x1 + }; + struct kMouseClick { + uint32_t mouse_button; // 0x0 = left, 0x1 = middle, 0x2 = right + uint32_t is_doubleclick; + uint32_t unknown_type_screen_pos; + uint32_t h000c; + uint32_t h0010; + }; + enum ActionState : uint32_t { + MouseDown = 0x6, // 0x6 + MouseUp = 0x7, // 0x7 + MouseClick = 0x8, // 0x8 + MouseDoubleClick = 0x9, // 0x9 + DragRelease = 0xb, // 0xb + KeyDown = 0xe // 0xe + }; + struct kMouseAction { + uint32_t frame_id; + uint32_t child_offset_id; + ActionState current_state; + void* wparam = 0; + void* lparam = 0; + }; + struct kWriteToChatLog { + GW::Chat::Channel channel; + wchar_t* message; + GW::Chat::Channel channel_dupe; + }; + struct kPlayerChatMessage { + GW::Chat::Channel channel; + wchar_t* message; + uint32_t player_number; + }; + + struct kInteractAgent { + uint32_t agent_id; + bool call_target; + }; + + struct kSendChangeTarget { + uint32_t target_id; + uint32_t auto_target_id; + }; + + struct kSendCallTarget { + CallTargetType call_type; + uint32_t agent_id; + }; + + struct kGetColor { + Chat::Color* color; + GW::Chat::Channel channel; + }; + + struct kWriteToChatLogWithSender { + uint32_t channel; + wchar_t* message; + wchar_t* sender_enc; + }; + + struct kSendPingWeaponSet { + uint32_t agent_id; + uint32_t weapon_item_id; + uint32_t offhand_item_id; + }; + struct kSendMoveItem { + uint32_t item_id; + uint32_t quantity; + uint32_t bag_index; + uint32_t slot; + }; + struct kSendMerchantRequestQuote { + Merchant::TransactionType type; + uint32_t item_id; + }; + struct kSendMerchantTransactItem { + Merchant::TransactionType type; + uint32_t h0004; + Merchant::QuoteInfo give; + uint32_t gold_recv; + Merchant::QuoteInfo recv; + }; + struct kSendChatMessage { + wchar_t* message; + uint32_t agent_id; + }; + struct kLogChatMessage { + wchar_t* message; + GW::Chat::Channel channel; + }; + struct kRecvWhisper { + uint32_t transaction_id; + wchar_t* from; + wchar_t* message; + }; + struct kStartWhisper { + wchar_t* player_name; + }; + struct kCompassDraw { + uint32_t player_number; + uint32_t session_id; + uint32_t number_of_points; + CompassPoint* points; + }; + struct kObjectiveAdd { + uint32_t objective_id; + wchar_t* name; + uint32_t type; + }; + struct kObjectiveComplete { + uint32_t objective_id; + }; + struct kObjectiveUpdated { + uint32_t objective_id; + }; + // Straight passthru of GW::Packets::StoC::ItemGeneral + struct kItemUpdated { + uint32_t item_id; + uint32_t model_file_id; + uint32_t type; + uint32_t unk1; + uint32_t extra_id; // Dye color + uint32_t materials; + uint32_t unk2; + uint32_t interaction; // Flags + uint32_t price; + uint32_t model_id; + uint32_t quantity; + wchar_t* enc_name; + uint32_t mod_struct_size; + uint32_t* mod_struct; + }; + struct kInventorySlotUpdated { + uint32_t unk; + uint32_t item_id; + uint32_t bag_index; + uint32_t slot_id; + }; + struct kSendWorldAction { + WorldActionId action_id; + GW::AgentID agent_id; + bool suppress_call_target; // 1 to block "I'm targetting X", but will also only trigger if the key thing is down + }; + struct kAllyOrGuildMessage { + GW::Chat::Channel channel; + wchar_t* message; + wchar_t* sender; + wchar_t* guild_tag; + }; + } + } +} diff --git a/Dependencies/GWCA/Include/GWCA/Context/MapContext.h b/Dependencies/GWCA/Include/GWCA/Context/MapContext.h index ea6a4222..c7a0fac8 100644 --- a/Dependencies/GWCA/Include/GWCA/Context/MapContext.h +++ b/Dependencies/GWCA/Include/GWCA/Context/MapContext.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -68,8 +69,7 @@ namespace GW { }; static_assert(sizeof(MapStaticData) == 0xA0, "struct MapStaticData has incorrect size"); - // Those are planes that are blocked and can be unblocked at runtime. e.g., the gates in foundry - // Those aren't in the dat file, but sent from the server + // Planes blocked but unblockable at runtime (e.g. the Foundry gates); sent by the server, not in the dat file. typedef BaseArray BlockedPlaneArray; static_assert(sizeof(BlockedPlaneArray) == 0xC, "struct BlockedPlaneArray has incorrect size"); @@ -123,9 +123,7 @@ namespace GW { }; static_assert(sizeof(PathContext) == 0x94, "struct PathContext has incorrect size"); - // The game can optionally load a DLL to do the path finding. - // The DLL is named "PathEngine.dll", but not clear if it's a 3rd party or just their name - // for development. + // The game can optionally load "PathEngine.dll" for path finding; unclear if third party or their own dev name. struct PathEngineContext { /* +h0000 */ void **vtable; /* +h0004 */ uint32_t h0004; @@ -136,6 +134,102 @@ namespace GW { }; static_assert(sizeof(PathEngineContext) == 0x18, "struct PathEngineContext has incorrect size"); + // Point lights baked into the map, parsed from its "LITE" chunk by Engine\Map\MapLight.cpp. + // Torches, braziers and campfires are these, not props with a glow texture. + struct MapLightDescriptor { + /* +h0000 */ Vec3f position; + /* +h000C */ uint8_t red; + /* +h000D */ uint8_t green; + /* +h000E */ uint8_t blue; + /* +h000F */ uint8_t h000F; + /* +h0010 */ float intensity; // colour bytes are premultiplied by intensity / 255 when the light is built + /* +h0014 */ float inner_range; + /* +h0018 */ float outer_range; + }; + static_assert(sizeof(MapLightDescriptor) == 0x1C, "struct MapLightDescriptor has incorrect size"); + + // Field layout taken from MapLightApplyDescriptor, which hands each field straight to a GrLight setter: + // GrLightSetColour(light, &desc->red, desc->intensity), GrLightSetPosition(light, &desc->position, 0), + // GrLightSetRange(light, desc->inner_range, desc->outer_range) + struct MapLightContext { + /* +h0000 */ Array descriptors; + /* +h0010 */ Array lights; // HGrLight handles, parallel to descriptors (same count) + }; + static_assert(sizeof(MapLightContext) == 0x20, "struct MapLightContext has incorrect size"); + + // Only the fields below are confirmed; the real object is at least 0x1A0 bytes, so no size assert. + // Only present when MapContext::flags bit 1 is set. + struct MapWaterContext { + /* +h0000 */ uint32_t h0000[0x2b]; + /* +h00AC */ uint32_t scene_program; // the single handle water contributes to the scene program list + /* +h00B0 */ uint32_t h00B0[0xc]; + /* +h00E0 */ uint32_t shader_programs[2]; // two of the four per-map shader programs, chosen by quality + /* +h00E8 */ uint32_t h00E8; + /* +h00EC */ float plane_z; // world height of the water plane + /* +h00F0 */ uint32_t h00F0[0x2a]; + /* +h0198 */ uint32_t shader_programs2[2]; // the other two quality variants + }; + + // === terrain textures / baked terrain shadows === + // + // GW bakes the map's static terrain shadows into the terrain textures, and streams them in + // per tile. The data lives in two layers, both reachable from the terrain texture context: + // 1. a per-texel bitmask, entropy coded per tile and decoded on demand into a scratch + // buffer (soft edges, detail) - TrnTexShadowDecompressTile; + // 2. a per-block "this whole block is in shadow" bitmask, held in the map's own data and + // tested first - a set bit fills the block flat with the shadow colour without ever + // consulting layer 1 - TrnTexComposeTileLighting. + // Clearing only layer 1 leaves the solid interiors of large shadows behind, which is what + // makes layer 2 worth documenting. + + // 32x32 blocks, one bit each, MSB first, 4 bytes per row; 0x80 bytes in total. + // Quality 0 walks it as two 16-row halves (the second at +0x40), every other quality walks + // all 32 rows in one pass - both reach exactly 0x80 bytes, so that is the whole mask. + typedef uint8_t TerrainBlockShadowMask[0x80]; + + // One per terrain tile, indexed by `shadow_grid_width * tile_y + tile_x`. + struct TerrainShadowRecord { + /* +h0000 */ void* compressed_stream; // layer 1, entropy coded, decoded per tile + /* +h0004 */ void* compressed_stream_end; + /* +h0008 */ TerrainBlockShadowMask* block_mask; // layer 2, the map's own data - not scratch + }; + static_assert(sizeof(TerrainShadowRecord) == 0xC, "struct TerrainShadowRecord has incorrect size"); + + struct TerrainTexTile { + /* +h0000 */ uint32_t h0000[3]; + /* +h000C */ uint32_t tile_x; + /* +h0010 */ uint32_t tile_y; + /* +h0014 */ uint32_t state; // set to 2 once the tile's shadow layer has been decoded + /* +h0018 */ uint32_t h0018; // zeroed alongside the state above + }; + + // A single streamed terrain texture. Only the two fields below are confirmed, so no size assert. + struct TerrainTexture { + /* +h0000 */ uint32_t h0000[0x27]; + /* +h009C */ uint32_t stream_flags; // bit 0x800: decompress + upload still pending + /* +h00A0 */ uint32_t h00A0[0x37]; + /* +h017C */ TerrainTexTile* tile; // tagged pointer: null, or bit 0 set, means no tile + }; + + // The map-wide terrain texture context (`TrnTex`). Partially mapped, so no size assert. + struct TerrainTexContext { + /* +h0000 */ uint32_t h0000[0x2a]; + // The bound tested by the game itself before indexing, so a tile outside the grid is + // simply "no baked shadow" rather than a read off the end. + /* +h00A8 */ BaseArray shadow_records; + // Row stride for the index above. The three fields at +0xA8/+0xB0/+0xB4 read equally well + // as an Array whose m_param happens to be the width; naming it is + // the more useful of two identical layouts. + /* +h00B4 */ uint32_t shadow_grid_width; + /* +h00B8 */ uint32_t h00B8[0x10b]; + // Scratch, shared by every tile this context streams: layer 1 is decoded here and + // uploaded, then the buffer is reused for the next tile. 0x110 rows of 0x22 bytes = + // 272x272 bits, one per terrain texel. A SET bit is shadow, a clear bit is lit. + /* +h04E4 */ uint8_t decoded_shadow_tile[0x110][0x22]; + }; + // Self-check on the padding above; the real object is at least this big, not exactly. + static_assert(sizeof(TerrainTexContext) == 0x2904, "struct TerrainTexContext has incorrect layout"); + struct MapContext { /* +h0000 */ uint32_t map_type; // less than 4 /* +h0004 */ Vec2f start_pos; @@ -149,8 +243,8 @@ namespace GW { /* +h0078 */ PathEngineContext* path_engine; /* +h007C */ PropsContext* props; /* +h0080 */ uint32_t h0080; - /* +h0084 */ void* terrain; - /* +h0088 */ uint32_t h0088; + /* +h0084 */ void* terrain; // relationship to TerrainTexContext is unconfirmed + /* +h0088 */ void* collision; // "Collision" map chunk (Engine\Map\Collision\CollApi.cpp) /* +h008C */ GW::Constants::MapID map_id; /* +h0090 */ uint32_t h0090; /* +h0094 */ uint32_t h0094; @@ -161,37 +255,31 @@ namespace GW { /* +h00A8 */ uint32_t h00A8; /* +h00AC */ uint32_t h00AC; /* +h00B0 */ uint32_t h00B0; - /* +h00B4 */ uint32_t h00B4; - /* +h00B8 */ uint32_t h00B8; - /* +h00BC */ uint32_t h00BC; - /* +h00C0 */ uint32_t h00C0; - /* +h00C4 */ uint32_t h00C4; - /* +h00C8 */ uint32_t h00C8; - /* +h00CC */ uint32_t h00CC; - /* +h00D0 */ uint32_t h00D0; - /* +h00D4 */ uint32_t h00D4; + /* +h00B4 */ Vec3f view_eye; // cached by GmWorldUpdateView + /* +h00C0 */ Vec3f view_target; + /* +h00CC */ Vec3f view_up; /* +h00D8 */ uint32_t h00D8; - /* +h00DC */ uint32_t h00DC; + /* +h00DC */ uint32_t view_flags; // bit 0: view has been updated this frame /* +h00E0 */ uint32_t h00E0; /* +h00E4 */ uint32_t h00E4; /* +h00E8 */ uint32_t h00E8; - /* +h00EC */ uint32_t h00EC; + /* +h00EC */ MapLightContext* lights; // "Light" map chunk /* +h00F0 */ uint32_t h00F0; /* +h00F4 */ uint32_t h00F4; /* +h00F8 */ uint32_t h00F8; /* +h00FC */ uint32_t h00FC; /* +h0100 */ uint32_t h0100; /* +h0104 */ uint32_t h0104; - /* +h0108 */ uint32_t h0108; + /* +h0108 */ uint32_t flags; // bit 1: map has water /* +h010C */ uint32_t h010C; /* +h0110 */ uint32_t h0110; /* +h0114 */ uint32_t h0114; /* +h0118 */ uint32_t h0118; /* +h011C */ uint32_t h011C; - /* +h0120 */ uint32_t h0120; + /* +h0120 */ void* shore; // "Shore" map chunk, only loaded when flags bit 1 is set /* +h0124 */ uint32_t h0124; /* +h0128 */ uint32_t h0128; - /* +h012C */ uint32_t h012C; + /* +h012C */ MapWaterContext* water; /* +h0130 */ void* zones; /* +h0134 */ uint32_t h0134; }; diff --git a/Dependencies/GWCA/Include/GWCA/Context/PreGameContext.h b/Dependencies/GWCA/Include/GWCA/Context/PreGameContext.h index 3e196fdd..ac91aaf4 100644 --- a/Dependencies/GWCA/Include/GWCA/Context/PreGameContext.h +++ b/Dependencies/GWCA/Include/GWCA/Context/PreGameContext.h @@ -38,9 +38,7 @@ namespace GW { /* +h0148 */ GW::Array chars; }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetAvailableChars(); } diff --git a/Dependencies/GWCA/Include/GWCA/Context/TradeContext.h b/Dependencies/GWCA/Include/GWCA/Context/TradeContext.h index c6d0794f..02b58734 100644 --- a/Dependencies/GWCA/Include/GWCA/Context/TradeContext.h +++ b/Dependencies/GWCA/Include/GWCA/Context/TradeContext.h @@ -29,8 +29,7 @@ namespace GW { /* +h0010 */ TradePlayer player; /* +h0024 */ TradePlayer partner; - // bool GetPartnerAccepted(); - // bool GetPartnerOfferSent(); + // bool GetPartnerAccepted(); bool GetPartnerOfferSent(); bool GetIsTradeOffered() const { return (flags & TRADE_OFFER_SEND) != 0; } bool GetIsTradeInitiated() const { return (flags & TRADE_INITIATED) != 0; } diff --git a/Dependencies/GWCA/Include/GWCA/GWCAVersion.h b/Dependencies/GWCA/Include/GWCA/GWCAVersion.h index 1f9a7a58..4767a4c1 100644 --- a/Dependencies/GWCA/Include/GWCA/GWCAVersion.h +++ b/Dependencies/GWCA/Include/GWCA/GWCAVersion.h @@ -1,10 +1,10 @@ #pragma once #define GWCA_VERSION_MAJOR 4 -#define GWCA_VERSION_MINOR 7 -#define GWCA_VERSION_PATCH 2 -#define GWCA_VERSION_BUILD 3 -#define GWCA_VERSION "4.7.2.3" +#define GWCA_VERSION_MINOR 8 +#define GWCA_VERSION_PATCH 5 +#define GWCA_VERSION_BUILD 0 +#define GWCA_VERSION "4.8.5.0" namespace GWCA { constexpr int VersionMajor = GWCA_VERSION_MAJOR; diff --git a/Dependencies/GWCA/Include/GWCA/GWCAVersion.h.in b/Dependencies/GWCA/Include/GWCA/GWCAVersion.h.in new file mode 100644 index 00000000..e7401242 --- /dev/null +++ b/Dependencies/GWCA/Include/GWCA/GWCAVersion.h.in @@ -0,0 +1,15 @@ +#pragma once + +#define GWCA_VERSION_MAJOR @GWCA_VERSION_MAJOR@ +#define GWCA_VERSION_MINOR @GWCA_VERSION_MINOR@ +#define GWCA_VERSION_PATCH @GWCA_VERSION_PATCH@ +#define GWCA_VERSION_BUILD @GWCA_VERSION_BUILD@ +#define GWCA_VERSION "@GWCA_VERSION@" + +namespace GWCA { + constexpr int VersionMajor = GWCA_VERSION_MAJOR; + constexpr int VersionMinor = GWCA_VERSION_MINOR; + constexpr int VersionPatch = GWCA_VERSION_PATCH; + constexpr int VersionBuild = GWCA_VERSION_BUILD; + constexpr const char* Version = GWCA_VERSION; +} \ No newline at end of file diff --git a/Dependencies/GWCA/Include/GWCA/GameContainers/ObjectPool.h b/Dependencies/GWCA/Include/GWCA/GameContainers/ObjectPool.h index 871a10c0..76782033 100644 --- a/Dependencies/GWCA/Include/GWCA/GameContainers/ObjectPool.h +++ b/Dependencies/GWCA/Include/GWCA/GameContainers/ObjectPool.h @@ -15,8 +15,4 @@ namespace GW { static_assert(sizeof(ObjectPool) == 0xC, "struct ObjectPool has incorrect size"); } -// The functions used to allocate looks like: -// void *__thiscall ObjectPool::Alloc(ObjectPool *pool, int32_t typesize, const char *typename); -// So, the typename & typesize are passed at every allocs, similar to `MemAlloc`. -// It's worth nothing that the minimum typesize is 4, though Guild Wars doesn't assert it, it just -// ends up crashing. This is due to the freeList pointer lists that are 4 bytes each. +// ObjectPool::Alloc(pool, typesize, typename) passes type info per alloc; minimum typesize is 4 (freeList pointers) or it crashes. diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/Agent.h b/Dependencies/GWCA/Include/GWCA/GameEntities/Agent.h index 06d65256..3906b565 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/Agent.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/Agent.h @@ -6,6 +6,7 @@ #include #include +#include namespace GW { typedef uint32_t AgentID; @@ -90,19 +91,12 @@ namespace GW { /* +h0109 */ uint8_t offhand_item_type; // Offhand item type for stance/animation /* +h010A */ uint16_t offhand_item_id; // Offhand item id for stance/animation - inline uint32_t GetType() { - return vtable->GetType(this); - } - inline bool RedrawEquipmentSlot(uint32_t slot) { - if (!(slot < _countof(items) && items[slot].model_file_id)) - return false; - return vtable->EquipItem(this, 0, slot), true; - } - inline bool UndrawEquipmentSlot(uint32_t slot) { - if (!(slot < _countof(items) && items[slot].model_file_id)) - return false; - return vtable->RemoveItem(this, 0, slot), true; - } + // These three call through the game's vtable (see Source/Agent.cpp) -- kept + // out of line so the wasm table-adoption dance stays an implementation + // detail rather than something every caller's translation unit compiles. + GWCA_API uint32_t GetType(); + GWCA_API bool RedrawEquipmentSlot(uint32_t slot); + GWCA_API bool UndrawEquipmentSlot(uint32_t slot); }; static_assert(sizeof(NPCEquipment) == 0x10C); @@ -145,6 +139,30 @@ namespace GW { struct AgentGadget; struct AgentLiving; + // Bits in GW::Agent::name_properties, which AvAgent.cpp recomputes name tag visibility from. + enum NameTagFlags : uint32_t { + // In the mouse pick list; cleared wholesale whenever that list is rebuilt. + NameTagFlags_Picked = 0x8, + // Moused-over agent: underlines the tag, glows the model, draws the selection decal. + NameTagFlags_Highlighted = 0x10, + // Within name tag draw distance (1500 gwinches from the camera). + NameTagFlags_InRange = 0x20, + // The evaluated target - manual target, else auto target. + NameTagFlags_EvaluatedTarget = 0x80, + // The manual target, while a different auto target exists. + NameTagFlags_ManualTarget = 0x100, + // Name tags globally suppressed (cutscenes, /hideui); refcounted by the client. + NameTagFlags_Suppressed = 0x200, + // Agent::type passes the persistent filter from the Guild Wars name tag options. + NameTagFlags_PassesFilter = 0x400, + // Dropped item reserved for another player, so it never gets a distance-based tag. + NameTagFlags_NotOwnedByPlayer = 0x800, + // Agent::type passes the transient filter, bound to the "show item names" key. + NameTagFlags_PassesTransientFilter = 0x1000, + // Name tag disabled for this agent regardless of any filter. + NameTagFlags_Disabled = 0x20000 + }; + struct Agent { /* +h0000 */ uint32_t* vtable; /* +h0004 */ uint32_t h0004; @@ -165,7 +183,7 @@ namespace GW { /* +h004C */ float rotation_angle; // Rotation in radians from East (-pi to pi) /* +h0050 */ float rotation_cos; // cosine of rotation /* +h0054 */ float rotation_sin; // sine of rotation - /* +h0058 */ uint32_t name_properties; // Bitmap basically telling what the agent is + /* +h0058 */ NameTagFlags name_properties; // Bitmap basically telling what the agent is /* +h005C */ uint32_t ground; /* +h0060 */ uint32_t h0060; /* +h0064 */ Vec3f terrain_normal; diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/Camera.h b/Dependencies/GWCA/Include/GWCA/GameEntities/Camera.h index f6941909..cc9b2f61 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/Camera.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/Camera.h @@ -65,8 +65,7 @@ namespace GW { float GetYaw() const { return yaw; } float GetPitch() const { return pitch; } - /// \brief This is not the FoV that GW uses to render - /// see GW::Render::GetFieldOfView() + // rief Not the FoV GW renders with -- see GW::Render::GetFieldOfView() float GetFieldOfView() const { return field_of_view; } bool IsCameraUnlocked() const { return camera_mode == 3; } diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/Frame.h b/Dependencies/GWCA/Include/GWCA/GameEntities/Frame.h index 003ea458..cb14c2b3 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/Frame.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/Frame.h @@ -65,7 +65,8 @@ namespace GW { GWCA_API SortHandler_pt GetSortHandler(); GWCA_API bool ClearItems(); GWCA_API bool RemoveItem(uint32_t child_offset_id); - GWCA_API bool AddItem(uint32_t flags, uint32_t child_offset_id, GW::UI::UIInteractionCallback callback); + // Returns the frame_id of the added item's frame, or 0 on failure. + GWCA_API uint32_t AddItem(uint32_t flags, uint32_t child_offset_id, GW::UI::UIInteractionCallback callback); GWCA_API uint32_t GetItemFrameId(uint32_t child_offset_id); GWCA_API bool GetSelectedValue(uint32_t* selected_value); @@ -89,13 +90,7 @@ namespace GW { FrameWithValue& operator=(const FrameWithValue&) = default; FrameWithValue& operator=(FrameWithValue&&) = default; - // Pure virtual: every concrete usage of this type is through one - // of its overriding subclasses below (ProgressBar, CheckboxFrame, - // DropdownFrame, SliderFrame all override both) — there was - // previously no definition anywhere in this codebase for a bare - // FrameWithValue's own GetValue/SetValue, which GCC's Itanium - // ABI (unlike MSVC's) requires in order to know where to emit a - // vtable for the class ("key function" rule). + // Pure virtual: GCC's Itanium ABI needs a key function to emit the vtable, and every concrete use is via an overriding subclass. virtual uint32_t GetValue() = 0; virtual bool SetValue(uint32_t value) = 0; }; diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/Item.h b/Dependencies/GWCA/Include/GWCA/GameEntities/Item.h index 6504f24a..64e7d1b4 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/Item.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/Item.h @@ -50,15 +50,7 @@ namespace GW { static_assert(sizeof(DyeInfo) == 3, "struct DyeInfo has incorrect size"); struct ItemData { - // No default member initializers: this type is used as a member - // of a nested anonymous struct/union in Agent.h - // (NPCEquipment) — MSVC permits default member initializers - // there (making the implicit default constructor non-trivial), - // but GCC rejects any member with a non-trivial constructor in - // an anonymous struct/union. ItemData is a raw overlay onto - // existing game memory throughout this codebase, never - // meaningfully default-constructed for its own sake, so this - // doesn't change any real behavior. + // No default member initializers: GCC rejects non-trivial members in the anonymous struct/union in Agent.h that uses this. uint32_t model_file_id; GW::Constants::ItemType type; GW::DyeInfo dye; diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/NPC.h b/Dependencies/GWCA/Include/GWCA/GameEntities/NPC.h index 44375255..c020cce7 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/NPC.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/NPC.h @@ -24,8 +24,7 @@ namespace GW { /* +h0014 */ GW::Constants::Profession primary; /* +h0018 */ GW::Constants::Profession secondary; /* +h001C */ uint8_t default_level; - // +h001D uint8_t padding; - // +h001E uint16_t padding; + // +h001D uint8_t padding; +h001E uint16_t padding; /* +h0020 */ wchar_t *name_enc; /* +h0024 */ uint32_t *model_files; /* +h0028 */ uint32_t files_count; // length of ModelFile diff --git a/Dependencies/GWCA/Include/GWCA/GameEntities/Pathing.h b/Dependencies/GWCA/Include/GWCA/GameEntities/Pathing.h index eea4aee9..6158fa6d 100644 --- a/Dependencies/GWCA/Include/GWCA/GameEntities/Pathing.h +++ b/Dependencies/GWCA/Include/GWCA/GameEntities/Pathing.h @@ -3,136 +3,141 @@ #include namespace GW { - struct PathingTrapezoid { // total: 0x30/48 - /* +h0000 */ uint32_t id; - union { - /* +h0004 */ PathingTrapezoid* adjacent[4]; - struct { - /* +h0004 */ PathingTrapezoid* top_left; - /* +h0008 */ PathingTrapezoid* top_right; - /* +h000C */ PathingTrapezoid* bottom_left; - /* +h0010 */ PathingTrapezoid* bottom_right; - }; - }; - /* +h0014 */ uint16_t portal_left; - /* +h0016 */ uint16_t portal_right; - /* +h0018 */ float XTL; - /* +h001C */ float XTR; - /* +h0020 */ float YT; - /* +h0024 */ float XBL; - /* +h0028 */ float XBR; - /* +h002C */ float YB; - }; - static_assert(sizeof(PathingTrapezoid) == 48, "struct PathingTrapezoid has incorrect size"); + struct PathingTrapezoid { // total: 0x30/48 + /* +h0000 */ uint32_t id; + union { + /* +h0004 */ PathingTrapezoid* adjacent[4]; + struct { + /* +h0004 */ PathingTrapezoid* top_left; + /* +h0008 */ PathingTrapezoid* top_right; + /* +h000C */ PathingTrapezoid* bottom_left; + /* +h0010 */ PathingTrapezoid* bottom_right; + }; + }; + /* +h0014 */ uint16_t portal_left; + /* +h0016 */ uint16_t portal_right; + /* +h0018 */ float XTL; + /* +h001C */ float XTR; + /* +h0020 */ float YT; + /* +h0024 */ float XBL; + /* +h0028 */ float XBR; + /* +h002C */ float YB; + }; + static_assert(sizeof(PathingTrapezoid) == 48, "struct PathingTrapezoid has incorrect size"); - struct Node { - /* +h0000 */ uint32_t type; //XNode = 0, YNode = 1, SinkNode = 2 - /* +h0004 */ uint32_t id; - }; + struct Node { + /* +h0000 */ uint32_t type; //XNode = 0, YNode = 1, SinkNode = 2 + /* +h0004 */ uint32_t id; + }; - struct XNode : Node { // type = 0 - /* +h0008 */ Vec2f pos; - /* +h0010 */ Vec2f dir; - /* +h0018 */ Node *left; - /* +h001C */ Node *right; - }; - static_assert(sizeof(XNode) == 32, "struct XNode has incorrect size"); + struct XNode : Node { // type = 0 + /* +h0008 */ Vec2f pos; + /* +h0010 */ Vec2f dir; + /* +h0018 */ Node* left; + /* +h001C */ Node* right; + }; + static_assert(sizeof(XNode) == 32, "struct XNode has incorrect size"); - struct YNode : Node { // type = 1 - /* +h0008 */ Vec2f pos; - /* +h0010 */ Node *above; - /* +h0014 */ Node *below; - }; - static_assert(sizeof(YNode) == 24, "struct YNode has incorrect size"); + struct YNode : Node { // type = 1 + /* +h0008 */ Vec2f pos; + /* +h0010 */ Node* above; + /* +h0014 */ Node* below; + }; + static_assert(sizeof(YNode) == 24, "struct YNode has incorrect size"); - struct SinkNode : Node { // type = 2 - /* +h0008 */ PathingTrapezoid *trapezoid; - }; - static_assert(sizeof(SinkNode) == 12, "struct SinkNode has incorrect size"); + struct SinkNode : Node { // type = 2 + /* +h0008 */ PathingTrapezoid* trapezoid; + }; + static_assert(sizeof(SinkNode) == 12, "struct SinkNode has incorrect size"); - struct Portal { // total: 0x14/20 - /* +h0000 */ uint16_t portal_plane; - /* +h0002 */ uint16_t neighbor_plane; - /* +h0004 */ uint32_t flags; // 0x4 => "Not used for path finding" - /* +h0008 */ Portal *pair; - /* +h000C */ uint32_t count; - /* +h0010 */ PathingTrapezoid **trapezoids; - }; - static_assert(sizeof(Portal) == 20, "struct Portal has incorrect size"); + struct Portal { // total: 0x14/20 + /* +h0000 */ uint16_t portal_plane; + /* +h0002 */ uint16_t neighbor_plane; + /* +h0004 */ uint32_t flags; // 0x4 => "Not used for path finding" + /* +h0008 */ Portal* pair; + /* +h000C */ uint32_t count; + /* +h0010 */ PathingTrapezoid** trapezoids; + }; + static_assert(sizeof(Portal) == 20, "struct Portal has incorrect size"); - struct PathingMap { // total: 0x54/84 - /* +h0000 */ uint32_t zplane; // ground plane = UINT_MAX, rest 0 based index - /* +h0004 */ uint32_t h0004; - /* +h0008 */ void *allocatedBuffer; // All following data are stored in a buffer allocated with a single MemAlloc. This is the pointer. - /* +h000C */ uint32_t h0010_count; // count of number h0010 - /* +h0010 */ uint32_t *h0010; // elements of this array are 8 bytes - /* +h0014 */ uint32_t trapezoid_count; - /* +h0018 */ PathingTrapezoid* trapezoids; - /* +h001C */ uint32_t sink_node_count; - /* +h0020 */ SinkNode *sink_nodes; - /* +h0024 */ uint32_t x_node_count; - /* +h0028 */ XNode *x_nodes; - /* +h002C */ uint32_t y_node_count; - /* +h0030 */ YNode *y_nodes; - /* +h0034 */ uint32_t portal_trapezoids_count; - /* +h0038 */ PathingTrapezoid **portal_trapezoids; - /* +h003C */ uint32_t portal_count; - /* +h0040 */ Portal *portals; - /* +h0044 */ Node *root_node; - /* +h0048 */ BaseArray dat_vectors; // this is an array of vectors read from the dat file. When reading the xnodes or ynodes from the dat file, an index will be used to get the pos. - }; - static_assert(sizeof(PathingMap) == 84, "struct PathingMap has incorrect size"); + struct PathingMap { // total: 0x54/84 + /* +h0000 */ uint32_t zplane; // ground plane = UINT_MAX, rest 0 based index + /* +h0004 */ uint32_t h0004; + /* +h0008 */ void* allocatedBuffer; // All following data are stored in a buffer allocated with a single MemAlloc. This is the pointer. + /* +h000C */ uint32_t h0010_count; // count of number h0010 + /* +h0010 */ uint32_t* h0010; // elements of this array are 8 bytes + /* +h0014 */ uint32_t trapezoid_count; + /* +h0018 */ PathingTrapezoid* trapezoids; + /* +h001C */ uint32_t sink_node_count; + /* +h0020 */ SinkNode* sink_nodes; + /* +h0024 */ uint32_t x_node_count; + /* +h0028 */ XNode* x_nodes; + /* +h002C */ uint32_t y_node_count; + /* +h0030 */ YNode* y_nodes; + /* +h0034 */ uint32_t portal_trapezoids_count; + /* +h0038 */ PathingTrapezoid** portal_trapezoids; + /* +h003C */ uint32_t portal_count; + /* +h0040 */ Portal* portals; + /* +h0044 */ Node* root_node; + /* +h0048 */ BaseArray dat_vectors; // this is an array of vectors read from the dat file. When reading the xnodes or ynodes from the dat file, an index will be used to get the pos. + }; + static_assert(sizeof(PathingMap) == 84, "struct PathingMap has incorrect size"); - struct PropByType { - uint32_t object_id; - uint32_t prop_index; - }; + struct PropByType { + uint32_t object_id; + uint32_t prop_index; + }; - struct PropModelInfo { - /* +h0000 */ uint32_t h0000; - /* +h0004 */ uint32_t h0004; - /* +h0008 */ uint32_t h0008; - /* +h000C */ uint32_t h000C; - /* +h0010 */ uint32_t h0010; - /* +h0014 */ uint32_t h0014; - }; - static_assert(sizeof(PropModelInfo) == 0x18, "struct PropModelInfo has incorrect size"); + // Per-model collision bounds, shared by every prop using that model. Offsets confirmed against + // the client's prop ray-pick, which builds a cylinder as + // radius = MapProp::scale * bounding_radius + // z = MapProp::position.z - bounds_z_offsets[n] * MapProp::scale + struct PropModelInfo { + /* +h0000 */ uint32_t h0000; + /* +h0004 */ const wchar_t* model_file_name; // hash-encoded; FileHashToFileId() turns it into a file id + /* +h0008 */ float bounding_radius; // model space; scale by MapProp::scale for world units + /* +h000C */ float bounds_z_offsets[2]; + /* +h0014 */ uint32_t h0014; + }; + static_assert(sizeof(PropModelInfo) == 0x18, "struct PropModelInfo has incorrect size"); - struct RecObject { - /* +h0000 */ void* vtable; - /* +h0004 */ uint32_t ref_count; - /* +h0008 */ uint32_t accessKey; // This is used by the game to make sure the data from the DAT matches the data in game - /* +h000c */ uint32_t standalone; - /* +h0010 */ uint32_t file_id; - /* +h0014 */ uint32_t stream_id; - /* +h0018 */ uint32_t flags; - /* +h001c */ uint32_t opened; - /* +h0020 */ uint32_t ref_count2; - }; - static_assert(sizeof(RecObject) == 0x24, "struct RecObject has incorrect size"); + struct RecObject { + /* +h0000 */ void* vtable; + /* +h0004 */ uint32_t ref_count; + /* +h0008 */ uint32_t accessKey; // This is used by the game to make sure the data from the DAT matches the data in game + /* +h000c */ uint32_t standalone; + /* +h0010 */ uint32_t file_id; + /* +h0014 */ uint32_t stream_id; + /* +h0018 */ uint32_t flags; + /* +h001c */ uint32_t opened; + /* +h0020 */ uint32_t ref_count2; + }; + static_assert(sizeof(RecObject) == 0x24, "struct RecObject has incorrect size"); - struct MapProp { // total: 0x54/84 - /* +h0000 */ uint32_t h0000[5]; - /* +h0014 */ uint32_t uptime_seconds; // time since spawned - /* +h0018 */ uint32_t h0018; - /* +h001C */ uint32_t prop_index; - /* +h0020 */ Vec3f position; - /* +h002C */ uint32_t model_file_id; - /* +h0030 */ uint32_t h0030[2]; - /* +h0038 */ float rotation_angle; - /* +h003C */ float rotation_cos; - /* +h003C */ float rotation_sin; - /* +h0040 */ uint32_t h0034[5]; - /* +h0058 */ RecObject* interactive_model; - /* +h005C */ uint32_t h005C[4]; - /* +h006C */ uint32_t appearance_bitmap; // Modified when animation changes - /* +h0070 */ uint32_t animation_bits; - /* +h0064 */ uint32_t h0064[5]; - /* +h0088 */ PropByType* prop_object_info; - /* +h008C */ uint32_t h008C; - }; + struct MapProp { // total: 0x90/144 + /* +h0000 */ uint32_t h0000[5]; + /* +h0014 */ uint32_t uptime_seconds; // time since spawned + /* +h0018 */ uint32_t h0018; + /* +h001C */ uint32_t prop_index; + /* +h0020 */ Vec3f position; + /* +h002C */ uint32_t model_file_id; + /* +h0030 */ uint32_t h0030[2]; + /* +h0038 */ float rotation_angle; + /* +h003C */ float rotation_cos; + /* +h0040 */ float rotation_sin; + /* +h0044 */ uint32_t h0044[3]; + /* +h0050 */ float scale; + /* +h0054 */ PropModelInfo* model_info; + /* +h0058 */ RecObject* interactive_model; + /* +h005C */ uint32_t h005C[4]; + /* +h006C */ uint32_t appearance_bitmap; // Modified when animation changes + /* +h0070 */ uint32_t animation_bits; + /* +h0074 */ uint32_t h0074[5]; + /* +h0088 */ PropByType* prop_object_info; + /* +h008C */ uint32_t h008C; + }; - static_assert(sizeof(MapProp) == 0x90, "struct MapProp has incorrect size"); + static_assert(sizeof(MapProp) == 0x90, "struct MapProp has incorrect size"); - typedef Array PathingMapArray; + typedef Array PathingMapArray; } diff --git a/Dependencies/GWCA/Include/GWCA/Managers/AgentMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/AgentMgr.h index b6171a4d..2ce35b4b 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/AgentMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/AgentMgr.h @@ -25,14 +25,18 @@ namespace GW { struct Module; extern Module AgentModule; +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4200) // nonstandard extension: zero-sized array in struct +#endif struct AgentEffect { uint32_t effect_type; uint32_t data_size; // usually 0x34, detemines length of data in bytes uint32_t data[]; }; +#ifdef _MSC_VER #pragma warning(pop) +#endif enum class WorldActionId : uint32_t { InteractEnemy, @@ -58,106 +62,107 @@ namespace GW { Include_Enemy = 0x000008, Include_SpiritPet = 0x000010, Accept_ActiveState = 0x000020, - Include_Minion = 0x000040, - Include_NPCMinipet = 0x000080, - Accept_Player = 0x000100, - Type_Gadget = 0x000200, - Type_Item = 0x000400, - Exclude_DeadAlly = 0x000800, - Exclude_DeadNeutral = 0x001000, - Exclude_DeadEnemy = 0x002000, - Exclude_DeadSpiritPet = 0x004000, - Exclude_DeadMinion = 0x008000, - Exclude_DeadNPCMinipet = 0x010000, - Exclude_UsedCorpse = 0x020000, - Exclude_AliveAlly = 0x040000, - Exclude_AliveNeutral = 0x080000, - Exclude_AliveEnemy = 0x100000, - Exclude_AliveSpiritPet = 0x200000, - Exclude_AliveMinion = 0x400000, - Exclude_AliveNPCMinipet = 0x800000, - Exclude_BeingObserved = 0x2000000, - ZeroPriority = 0x4000000, - NPCMinipet_ZeroPriority = 0x8000000, - }; - - inline AgentTargetFlags operator|(AgentTargetFlags a, AgentTargetFlags b) { - return static_cast(static_cast(a) | static_cast(b)); - } - inline AgentTargetFlags operator&(AgentTargetFlags a, AgentTargetFlags b) { - return static_cast(static_cast(a) & static_cast(b)); - } - inline AgentTargetFlags operator~(AgentTargetFlags a) { - return static_cast(~static_cast(a)); - } - - namespace TargetFilter { - const AgentTargetFlags Enemies = static_cast( - Include_Enemy | Exclude_DeadEnemy); - - const AgentTargetFlags Allies = static_cast( - Include_Ally | Accept_Player | Exclude_DeadAlly); - - const AgentTargetFlags Corpses = static_cast( - Include_Enemy | Exclude_AliveEnemy | Exclude_UsedCorpse); - - const AgentTargetFlags Items = static_cast( - Type_Item); - - const AgentTargetFlags Gadgets = static_cast( - Type_Gadget); - - const AgentTargetFlags AnyLiving = static_cast( - Include_Ally | Include_Neutral | Include_Enemy | - Include_SpiritPet | Include_Minion | Include_NPCMinipet | - Accept_Player | - Exclude_DeadAlly | Exclude_DeadNeutral | Exclude_DeadEnemy | - Exclude_DeadSpiritPet | Exclude_DeadMinion | Exclude_DeadNPCMinipet); - - const AgentTargetFlags Any = static_cast( - Include_Ally | Include_Neutral | Include_Enemy | - Include_SpiritPet | Include_Minion | Include_NPCMinipet | - Accept_Player | Accept_HasQuest | - Type_Gadget | Type_Item); - } - - - namespace Agents { - typedef HookCallback AgentEffectCallback; - - // === Dialogs === - // Same as pressing button (id) while talking to an NPC. - GWCA_API bool SendDialog(uint32_t dialog_id); - - // === Agent Array === - - // Get Agent ID of currently observed agent - GWCA_API uint32_t GetObservingId(); - // Get Agent ID of client's logged in player - GWCA_API uint32_t GetControlledCharacterId(); - // Get Agent ID of current target - GWCA_API uint32_t GetTargetId(); - // Get Agent ID of current evaluated target - either auto target or actual target - GWCA_API uint32_t GetEvaluatedTargetId(); - - // Returns Agentstruct Array of agents in compass range, full structs. - GWCA_API AgentArray* GetAgentArray(); - - // Get AgentArray Structures of player or target. - GWCA_API Agent *GetAgentByID(uint32_t id); - // Get agent that we're currently observing - inline Agent *GetObservingAgent() { return GetAgentByID(GetObservingId()); } - // Get Agent of current target - inline Agent *GetTarget() { return GetAgentByID(GetTargetId()); } - // Get Agent of current evaluated target - either auto target or actual target - inline Agent *GetEvaluatedTarget() { return GetAgentByID(GetEvaluatedTargetId()); } - - GWCA_API Agent *GetPlayerByID(uint32_t player_id); - - // Get Agent of current logged in character - GWCA_API AgentLiving* GetControlledCharacter(); - - GWCA_API bool GetAgentMatchesFlags(const Agent*, AgentTargetFlags flags = TargetFilter::Any); + Include_Minion = 0x000040, + Include_NPCMinipet = 0x000080, + Accept_Player = 0x000100, + Type_Gadget = 0x000200, + Type_Item = 0x000400, + Exclude_DeadAlly = 0x000800, + Exclude_DeadNeutral = 0x001000, + Exclude_DeadEnemy = 0x002000, + Exclude_DeadSpiritPet = 0x004000, + Exclude_DeadMinion = 0x008000, + Exclude_DeadNPCMinipet = 0x010000, + Exclude_UsedCorpse = 0x020000, + Exclude_AliveAlly = 0x040000, + Exclude_AliveNeutral = 0x080000, + Exclude_AliveEnemy = 0x100000, + Exclude_AliveSpiritPet = 0x200000, + Exclude_AliveMinion = 0x400000, + Exclude_AliveNPCMinipet = 0x800000, + Exclude_BeingObserved = 0x2000000, + ZeroPriority = 0x4000000, + NPCMinipet_ZeroPriority = 0x8000000, + }; + + inline AgentTargetFlags operator|(AgentTargetFlags a, AgentTargetFlags b) { + return static_cast(static_cast(a) | static_cast(b)); + } + inline AgentTargetFlags operator&(AgentTargetFlags a, AgentTargetFlags b) { + return static_cast(static_cast(a) & static_cast(b)); + } + inline AgentTargetFlags operator~(AgentTargetFlags a) { + return static_cast(~static_cast(a)); + } + + namespace TargetFilter { + const AgentTargetFlags Enemies = static_cast( + Include_Enemy | Exclude_DeadEnemy); + + const AgentTargetFlags Allies = static_cast( + Include_Ally | Accept_Player | Exclude_DeadAlly); + + const AgentTargetFlags Corpses = static_cast( + Include_Enemy | Exclude_AliveEnemy | Exclude_UsedCorpse); + + const AgentTargetFlags Items = static_cast( + Type_Item); + + const AgentTargetFlags Gadgets = static_cast( + Type_Gadget); + + const AgentTargetFlags AnyLiving = static_cast( + Include_Ally | Include_Neutral | Include_Enemy | + Include_SpiritPet | Include_Minion | Include_NPCMinipet | + Accept_Player | + Exclude_DeadAlly | Exclude_DeadNeutral | Exclude_DeadEnemy | + Exclude_DeadSpiritPet | Exclude_DeadMinion | Exclude_DeadNPCMinipet); + + const AgentTargetFlags Any = static_cast( + Include_Ally | Include_Neutral | Include_Enemy | + Include_SpiritPet | Include_Minion | Include_NPCMinipet | + Accept_Player | Accept_HasQuest | + Type_Gadget | Type_Item); + } + + + namespace Agents { + typedef HookCallback AgentEffectCallback; + + // Dialogs -- same as pressing button (id) while talking to an NPC. + GWCA_API bool SendDialog(uint32_t dialog_id); + + // === Agent Array === + + // Get Agent ID of currently observed agent + GWCA_API uint32_t GetObservingId(); + // Get Agent ID of client's logged in player + GWCA_API uint32_t GetControlledCharacterId(); + // Get Agent ID of current target + GWCA_API uint32_t GetTargetId(); + // Get Agent ID of current evaluated target - either auto target or actual target + GWCA_API uint32_t GetEvaluatedTargetId(); + + // Returns Agentstruct Array of agents in compass range, full structs. + GWCA_API AgentArray* GetAgentArray(); + + // Get AgentArray Structures of player or target. + GWCA_API Agent* GetAgentByID(uint32_t id); + // Get agent that we're currently observing + inline Agent* GetObservingAgent() { return GetAgentByID(GetObservingId()); } + // Get Agent of current target + inline Agent* GetTarget() { return GetAgentByID(GetTargetId()); } + // Get Agent of current evaluated target - either auto target or actual target + inline Agent* GetEvaluatedTarget() { return GetAgentByID(GetEvaluatedTargetId()); } + + GWCA_API Agent* GetPlayerByID(uint32_t player_id); + + // Get Agent of current logged in character + GWCA_API AgentLiving* GetControlledCharacter(); + + GWCA_API bool GetAgentMatchesFlags(const Agent*, AgentTargetFlags flags = TargetFilter::Any); + + GWCA_API bool RefreshAgentNameTag(const Agent*); // Whether we're observing someone else GWCA_API bool IsObserving(); @@ -167,8 +172,7 @@ namespace GW { GWCA_API uint32_t GetAmountOfPlayersInInstance(); - // Returns array of alternate agent array that can be read beyond compass range. - // Holds limited info and needs to be explored more. + // Alternate agent array readable beyond compass range; holds limited info and needs more exploration. GWCA_API MapAgentArray* GetMapAgentArray(); GWCA_API uint32_t CountAllegianceInRange(GW::Constants::Allegiance allegiance, float sqr_range); @@ -187,8 +191,7 @@ namespace GW { GWCA_API bool ChangeTarget(const Agent *agent); GWCA_API bool ChangeTarget(AgentID agent_id); - // Move to specified coordinates. - // Note: will do nothing if coordinate is outside the map! + // Move to specified coordinates. Does nothing if the coordinate is outside the map. GWCA_API bool Move(float x, float y, uint32_t zplane = 0); GWCA_API bool Move(GamePos pos); @@ -205,18 +208,9 @@ namespace GW { // Might be bugged, avoid to use. GWCA_API wchar_t* GetAgentEncName(const Agent* agent); GWCA_API wchar_t* GetAgentEncName(uint32_t agent_id); - - GWCA_API void RegisterAgentEffectCallback( - HookEntry* entry, - const AgentEffectCallback& callback, - int altitude = -0x8000); - GWCA_API void RemoveFrameUIMessageCallback( - HookEntry* entry); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API bool SendDialog(uint32_t dialog_id); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/CameraMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/CameraMgr.h index bd05dfd1..52cc51a0 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/CameraMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/CameraMgr.h @@ -25,8 +25,7 @@ namespace GW { GWCA_API float GetFieldOfView(); GWCA_API float GetYaw(); - // ==== Camera patches ==== - // Unlock camera & return the new state of it + // Camera patches -- unlock the camera and return its new state. GWCA_API bool UnlockCam(bool flag); GWCA_API bool GetCameraUnlock(); @@ -34,9 +33,7 @@ namespace GW { GWCA_API bool SetFog(bool flag); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetCamera(); GWCA_API bool SetMaxDist(float dist); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/ChatMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/ChatMgr.h index b51d1425..c8d3e383 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/ChatMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/ChatMgr.h @@ -12,15 +12,19 @@ namespace GW { namespace Chat { typedef uint32_t Color; +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4200) +#endif struct ChatMessage { uint32_t channel; uint32_t unk1; FILETIME timestamp; wchar_t message[0]; }; +#ifdef _MSC_VER #pragma warning(pop) +#endif const size_t CHAT_LOG_LENGTH = 0x200; struct ChatBuffer { @@ -105,9 +109,7 @@ namespace GW { }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetChatLog(); GWCA_API bool AddToChatLog(wchar_t* message, uint32_t channel); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/EffectMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/EffectMgr.h index f4b89608..3d3071c7 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/EffectMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/EffectMgr.h @@ -20,9 +20,7 @@ namespace GW { extern Module EffectModule; namespace Effects { - // Returns current level of intoxication, 0-5 scale. - // If > 0 then skills that benefit from drunk will work. - // Important: requires SetupPostProcessingEffectHook() above. + // Current intoxication level, 0-5; above 0 makes drunk-benefit skills work. Requires SetupPostProcessingEffectHook(). GWCA_API uint32_t GetAlcoholLevel(); // Have fun with this ;)))))))))) @@ -59,9 +57,7 @@ namespace GW { GWCA_API Buff *GetPlayerBuffBySkillId(Constants::SkillID skill_id); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API uint32_t GetAlcoholLevel(); GWCA_API void GetDrunkAf(float intensity, uint32_t tint); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/EventMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/EventMgr.h index 04da6627..7e08bafd 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/EventMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/EventMgr.h @@ -9,15 +9,65 @@ namespace GW { extern Module EventMgrModule; namespace EventMgr { + // Index into the game's m_handler array (EvtDispatch.cpp asserts id < EVENTS, which is 0x2f). + // Names below are read off Gw.exe: EvtOs.cpp translates each window message into one of these, + // EvtQueue.cpp drains them into the dispatcher, and every id the game itself registers a handler + // for was matched to that handler. Ids absent here are dispatched by the game but not yet identified. enum class EventID { - kRecvPing = 0x8, - kSendFriendState = 0x26, - kRecvFriendState = 0x2c, + kAppActivated = 0x0, // WM_ACTIVATEAPP; packet is one dword, non-zero when activated + kFrameTick = 0x1, // once per frame from the pump, packet is the elapsed milliseconds + kRenderFinished = 0x2, // once per frame after the render, packet is one dword + kCharTyped = 0x3, // WM_CHAR: {character, repeat, modifier keys} + kCursorVisibility = 0x5, // packet is one dword, non-zero to show the cursor + kShutdown = 0x6, // posting this latches the event queue closed, so nothing dispatches after it + kScreenSizeChanged = 0x7, // {new width, new height, old width, old height}, raised inside the queue drain + kDriverError = 0x8, // the game's own handler logs "Driver internal error encountered." and quits + kRecvPing = kDriverError, // legacy GWCA name for 0x8; the game does not use this id for pings + kDpiChanged = 0xa, // WM_DPICHANGED: {x dpi, y dpi} + kGamepadAttached = 0xb, // the four below come from the same controller poll in EvtOs.cpp + kGamepadState = 0xc, // one dword + kGamepadAxis = 0xd, // four dwords + kGamepadButtonDown = 0xe, // {button, state} + kGamepadButtonUp = 0xf, // {button, state} + kCharTypedInjected = 0x13, // same shape as kCharTyped, from the game's own WM_APP+3 injection + kKeyDown = 0x1a, // {key, repeat, modifier keys}; the engine records the key as held here + kKeyUp = 0x1b, // same shape; the engine clears the held bit here + kFocusLost = 0x1c, // WM_KILLFOCUS, no packet + kMouseMoveRelative = 0x1d, // {button, 0, x, y, button mask, modifier keys} while the mouse is captured + kMouseButtonDown = 0x1e, // same shape as kMouseMoveRelative + kMouseLeave = 0x1f, // WM_MOUSELEAVE, no packet + kMouseMove = 0x20, // same shape as kMouseMoveRelative, only sent when the position changed + kMouseButtonUp = 0x21, // same shape; also synthesised for every held button when capture is lost + kMouseWheel = 0x22, // same shape, with the wheel delta in place of the button + kWindowMoved = 0x23, // WM_MOVE: {x, y} + // A shared channel: eight of the game's own subsystems register here, so filter on FriendEventType. + kFriend = 0x24, + kService = 0x25, // same packet shape as kFriend; FrApi forwards it as UI message 0x50 + kServiceAlt = 0x26, // same again, forwarded as UI message 0x51 + kFocusGained = 0x27, // WM_SETFOCUS, no packet + kWindowResized = 0x28, // WM_SIZE: {width, height} + kTouchDown = 0x2c, // the three touch ids carry {index, 0, x, y, touch mask} + kTouchMove = 0x2d, + kTouchUp = 0x2e, // also synthesised for every held touch when the sequence is cancelled + + // AddHandler rejects 0x2a outright, and the dispatcher drops anything >= 0x2f, so 0x2f and + // 0x30 are engine-internal: 0x30 is what makes the queue raise kScreenSizeChanged. + kReserved = 0x2a, kNone = 0xffff }; + // First dword of a kFriend packet, which is what the game's own handler switches over. + // These are the message ids FriendApi.cpp sends to the login frame, so they share that numbering. + enum class FriendEventType { + kStatusChanged = 0x26, + kLocationChanged = 0x28, + kFriendAddedOrRemoved = 0x2c + }; + + // (status, event_id, packet_bytes, packet_bytes_len). Altitude <= 0 runs before the game handles it, > 0 after. typedef HookCallback EventCallback; + GWCA_API void RegisterEventCallback( HookEntry *entry, EventID event_id, @@ -28,6 +78,7 @@ namespace GW { HookEntry *entry, EventID event_id = EventID::kNone); - GWCA_API bool SendEventMessage(EventID, void*); + // Took a length all along; the two-parameter declaration this replaces never had a definition to link against. + GWCA_API bool SendEventMessage(EventID event_id, void* packet_bytes, uint32_t packet_bytes_len); }; } diff --git a/Dependencies/GWCA/Include/GWCA/Managers/FriendListMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/FriendListMgr.h index 75ec16d2..8c76d30c 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/FriendListMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/FriendListMgr.h @@ -27,14 +27,15 @@ namespace GW { HookEntry* entry); GWCA_API bool AddFriend(const wchar_t* name, const wchar_t* alias = nullptr); GWCA_API bool AddIgnore(const wchar_t* name, const wchar_t* alias = nullptr); + // Fire-and-forget, like AddFriend: true means the request was queued (it + // may need the Friends window to finish opening across a few game ticks + // first), not that the row is gone by the time this returns. GWCA_API bool RemoveFriend(Friend* _friend); GWCA_API bool ChangeFriendType(Friend* _friend, FriendType type); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetFriendList(); GWCA_API void* GetFriendByIndex(uint32_t index); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/GameThreadMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/GameThreadMgr.h index 4311ac1a..08e541d9 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/GameThreadMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/GameThreadMgr.h @@ -16,8 +16,7 @@ namespace GW { GWCA_API void ClearCalls(); - // force_enqueue = false; will check if we're already in the game thread, and run immediately if we are. - // force_enqueue = true; enqueue the function for the next loop regardless + // force_enqueue = false runs immediately if already on the game thread; true always enqueues for the next loop. GWCA_API void Enqueue(std::function f, bool force_enqueue = false); typedef HookCallback<> GameThreadCallback; @@ -31,9 +30,7 @@ namespace GW { GWCA_API bool IsInGameThread(); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { typedef void(__cdecl* GW_GameThreadCallback)(); typedef void(__cdecl* GW_GameThreadHookCallback)(GW::HookStatus* status); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/GuildMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/GuildMgr.h index b916363e..e75b8b2a 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/GuildMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/GuildMgr.h @@ -21,9 +21,7 @@ namespace GW { }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetPlayerGuild(); GWCA_API void* GetCurrentGH(); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/ItemMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/ItemMgr.h index 0700550c..04d1b975 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/ItemMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/ItemMgr.h @@ -97,9 +97,7 @@ namespace GW { }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { // Lookups GWCA_API void* GetSalvageSessionInfo(); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/MapMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/MapMgr.h index d7b4cc11..7d48bc78 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/MapMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/MapMgr.h @@ -125,6 +125,8 @@ namespace GW { // Get the district number you are in. GWCA_API int GetDistrict(); + GWCA_API size_t GetDistrictCount(); + // Get time, in ms, since the instance you are residing in has been created. GWCA_API uint32_t GetInstanceTime(); @@ -140,8 +142,7 @@ namespace GW { GWCA_API Constants::Language LanguageFromDistrict(const GW::Constants::District _district); - // Returns array of icons (res shrines, quarries, traders, etc) on mission map. - // Look at MissionMapIcon struct for more info. + // Array of icons (res shrines, quarries, traders...) on the mission map; see MissionMapIcon. GWCA_API MissionMapIconArray* GetMissionMapIconArray(); // Returns pointer of collision trapezoid array. @@ -164,9 +165,7 @@ namespace GW { GWCA_API bool CancelEnterChallenge(); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API void* GetMissionMapContext(); GWCA_API void* GetWorldMapContext(); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/MemoryMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/MemoryMgr.h index ffc98d79..df31fc2f 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/MemoryMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/MemoryMgr.h @@ -16,9 +16,7 @@ namespace GW { GWCA_API HWND GetGWWindowHandle(); - // You probably don't want to use these functions. These are for allocating - // memory on the Guild Wars game heap, rather than your own heap. Memory allocated with - // these functions cannot be used with RAII and must be manually freed. USE AT YOUR OWN RISK. + // Allocates on the Guild Wars game heap, not yours: no RAII, must be freed manually. USE AT YOUR OWN RISK. GWCA_API void* MemAlloc(size_t size); GWCA_API void* MemRealloc(void* buf, size_t newSize); GWCA_API void MemFree(void* buf); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/Module.h b/Dependencies/GWCA/Include/GWCA/Managers/Module.h index bbde8281..08e56870 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/Module.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/Module.h @@ -8,8 +8,7 @@ namespace GW { void (*init_module)(); void (*exit_module)(); - // Call those from game thread to be safe - // Do not free trampoline + // Call these from the game thread to be safe, and do not free the trampoline. void (*enable_hooks)(); void (*disable_hooks)(); }; diff --git a/Dependencies/GWCA/Include/GWCA/Managers/PartyMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/PartyMgr.h index de13f32d..0bf05ef6 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/PartyMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/PartyMgr.h @@ -23,8 +23,7 @@ namespace GW { namespace PartyMgr { - // set or unset the fact that ticking will work as a toggle instead - // of showing a drop-down menu + // Set or unset whether ticking works as a toggle instead of showing a drop-down menu. GWCA_API void SetTickToggle(bool enable); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/PlayerMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/PlayerMgr.h index f18d70e8..031192ed 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/PlayerMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/PlayerMgr.h @@ -55,9 +55,7 @@ namespace GW { GWCA_API TitleClientData* GetTitleData(Constants::TitleID title_id); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API bool SetActiveTitle(uint32_t title_id); GWCA_API bool RemoveActiveTitle(); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/QuestMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/QuestMgr.h index a439dd6c..97958a57 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/QuestMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/QuestMgr.h @@ -42,9 +42,7 @@ namespace GW { }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API uint32_t GetActiveQuestId(); GWCA_API bool SetActiveQuestId(uint32_t quest_id); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/RenderMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/RenderMgr.h index f8b4029f..1b5188c3 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/RenderMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/RenderMgr.h @@ -2,6 +2,9 @@ #include +#include +#include + // forward declaration, we don't need to include the full directx header here struct IDirect3DDevice9; @@ -12,37 +15,69 @@ namespace GW { namespace Render { - typedef void(__cdecl* RenderCallback) (IDirect3DDevice9*); + // Runtime, not build-time: Gw.exe ships both backends and picks one via Param::GetFlag(10). + enum class Backend : uint32_t { + Unknown = 0, + D3D9, + GLES3, + }; - typedef struct Mat4x3f { - float _11; - float _12; - float _13; - float _14; - float _21; - float _22; - float _23; - float _24; - float _31; - float _32; - float _33; - float _34; - - - enum Flags { - Shear = 1 << 3 - }; - - uint32_t flags; - } Mat4x3f; - - enum Transform : int { - TRANSFORM_PROJECTION_MATRIX = 0, - TRANSFORM_MODEL_MATRIX = 1, - // TODO: - TRANSFORM_COUNT = 5 + GWCA_API Backend GetBackend(); + + // VirtualDeviceRenderer::renderer_mode -- all windowed variants, ordered as the settings dropdown lists them. + enum class RendererMode : uint32_t { + Windowed = 0, + WindowedBorderless = 1, + WindowedFullscreen = 2, }; + // Unknown values pass through rather than being coerced. + GWCA_API RendererMode GetRendererMode(); + + // GetDevice() is the Dx9 device, GetGlesDevice() the GLES3 one; pick with GetBackend(). The rest works on all three. + + // GLES3 device, captured from a hook since ddi_device is an id, not a pointer. Offsets confirmed against a running client. + using EGLSurface = void*; + using EGLContext = void*; + using EGLDisplay = void*; + + // The std140 block the fragment shader reads; the layout is exact, taken from the shader source embedded in the client. + struct GlFragmentRenderState { + /* +h0000 */ float texture_factor[4]; + /* +h0010 */ float fog_color[4]; + /* +h0020 */ float sampler_biases[2][4]; + /* +h0040 */ float bump_env_mat[8][4]; + /* +h00C0 */ float discard_settings[4]; + }; + static_assert(sizeof(GlFragmentRenderState) == 0xD0); + + // ArenaNet's own struct wrapping EGL handles, not a COM object -- you cannot call methods on it, only read and write cached state. + struct GlesDevice { + /* +h0000 */ uint8_t h0000[0x7D4]; + /* +h07D4 */ uint32_t dev_mode; // GR_MODE_* + /* +h07D8 */ uint8_t h07D8[0x28]; + /* +h0800 */ void* dev_window; // native window + /* +h0804 */ uint8_t h0804[0x8]; + /* +h080C */ EGLSurface dev_surface; + /* +h0810 */ uint32_t width; // live surface size + /* +h0814 */ uint32_t height; + /* +h0818 */ EGLContext dev_context; + /* +h081C */ uint8_t h081C[0x7D4]; + /* +h0FF0 */ GlFragmentRenderState fragment_state; + /* +h10C0 */ uint32_t uniform_buffer; // the UBO name + }; + static_assert(offsetof(GlesDevice, dev_mode) == 0x07D4); + static_assert(offsetof(GlesDevice, dev_window) == 0x0800); + static_assert(offsetof(GlesDevice, dev_surface) == 0x080C); + static_assert(offsetof(GlesDevice, dev_context) == 0x0818); + static_assert(offsetof(GlesDevice, fragment_state) == 0x0FF0); + static_assert(offsetof(GlesDevice, uniform_buffer) == 0x10C0); + + // Captured from a hook -- not reachable from the generic device. Null unless GetBackend() is GLES3. + GWCA_API GlesDevice* GetGlesDevice(); + + typedef void(__cdecl* RenderCallback) (IDirect3DDevice9*); + enum Metric : uint32_t { Metric0, Metric1, @@ -73,27 +108,17 @@ namespace GW { Count }; - // Careful, this doesn't return correct values if you have the U mission map, or other things open - // prefer to calculate the transformation matrix yourself using Render::GetFieldOfView() - [[deprecated]] GWCA_API Mat4x3f* GetTransform(Transform transform); - GWCA_API void EnableHooks(); // this returns the FoV used for rendering GWCA_API float GetFieldOfView(); - // Set up a callback for drawing on screen. - // Will be called after GW render. - // - // Important: if you use this, you should call GW::Terminate() - // or at least GW::Render::RestoreHooks() from within the callback + // Called after GW render each frame on D3D9 (End Scene); call GW::Terminate() or RestoreHooks() from within it. GWCA_API void SetRenderCallback(RenderCallback callback); GWCA_API RenderCallback GetRenderCallback(); - // Flush GW's deferred GR command queue so previously-submitted draws (e.g. the 3D - // world) are materialised into the back/depth buffer. No-op unless the queue is in - // a flushable state. Lets a render hook draw between GW's world and UI passes. + // Flush GW's deferred GR queue so submitted draws materialise, letting a render hook draw between the world and UI passes. GWCA_API void FlushCommandQueue(); // Can be used to get information like vsync status or monitor refresh rate of the renderer @@ -105,23 +130,38 @@ namespace GW { // Set a hard upper limit for frame rate. Actual limit may be lower (but not higher) depending on vsync/in-game preference GWCA_API bool SetFrameLimit(uint32_t value); - // Set up a callback for directx device reset + // D3D9 device reset. GLES has no lost-device concept; SetGlesResetCallback covers the corresponding event. GWCA_API void SetResetCallback(RenderCallback callback); + GWCA_API RenderCallback GetResetCallback(); + + // The same two events typed on the GLES device -- set whichever pair matches GetBackend(). + + // Fires each frame on GLES3 at the queue flush, so anything drawn goes out with that frame's commands. + typedef void(__cdecl* GlesRenderCallback)(GlesDevice* gles_device); - // Check if gw is in fullscreen - // Note: requires one or both callbacks to be set and called before - // Note: does not update while minimized - // Note: returns -1 if it doesn't know yet + GWCA_API void SetGlesRenderCallback(GlesRenderCallback callback); + GWCA_API GlesRenderCallback GetGlesRenderCallback(); + + // Fires when the GLES device is created or updated -- a state-change event, not per-frame, and where the pointer is captured. + GWCA_API void SetGlesResetCallback(GlesRenderCallback callback); + GWCA_API GlesRenderCallback GetGlesResetCallback(); + + // The same frame boundary as the render callbacks, without a device, for callers that only need the tick. + typedef void(__cdecl* FrameCallback)(); + + GWCA_API void SetFrameCallback(FrameCallback callback); + GWCA_API FrameCallback GetFrameCallback(); + + // Needs a callback set and called first, does not update while minimized, and returns -1 until known. GWCA_API int GetIsFullscreen(); GWCA_API bool SetFog(bool enabled); GWCA_API HWND GetWindowHandle(); + // Null unless GetBackend() == Backend::D3D9. GWCA_API IDirect3DDevice9* GetDevice(); - GWCA_API bool GetIsInRenderLoop(); - GWCA_API uint32_t GetViewportWidth(); GWCA_API uint32_t GetViewportHeight(); } diff --git a/Dependencies/GWCA/Include/GWCA/Managers/SkillbarMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/SkillbarMgr.h index 58afca96..0880e4be 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/SkillbarMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/SkillbarMgr.h @@ -36,9 +36,7 @@ namespace GW { }; static_assert(sizeof(SkillTemplate) == 140); - // Handlers run in altitude order around the hooked game function: altitude <= 0 before it - // (may set status->blocked to skip it), altitude > 0 after it. Handlers that produce a - // template write it into out_template and set *result to the game's success value. + // Handlers run in altitude order: <= 0 before the hooked function (may set status->blocked), > 0 after it. typedef HookCallback DecodeTemplateHeaderCallback; GWCA_API void RegisterDecodeTemplateHeaderCallback(HookEntry* entry, const DecodeTemplateHeaderCallback& callback, int altitude = -0x8000); @@ -56,22 +54,20 @@ namespace GW { GWCA_API void RemoveTemplateCallback(HookEntry* entry); - // Get the skill slot in the player bar of the player. - // Returns -1 if the skill is not there + // Skill slot in the player's bar, or -1 if the skill is not there. GWCA_API int GetSkillSlot(Constants::SkillID skill_id); // Use Skill in slot (Slot) on (Agent), optionally call that you are using said skill. GWCA_API bool UseSkill(uint32_t slot, uint32_t target = 0); - // Send raw packet to use skill with ID (SkillID). - // Same as above except the skillbar client struct will not be registered as casting. + // Raw packet to use skill ID; unlike above, the skillbar client struct is not registered as casting. GWCA_API bool UseSkillByID(uint32_t skill_id, uint32_t target = 0); // Get skill structure of said id, houses pretty much everything you would want to know about the skill. GWCA_API Skill* GetSkillConstantData(Constants::SkillID skill_id); // Number of entries in the client's skill constant data array; can exceed Constants::SkillID::Count when GW adds skills. - GWCA_API uint32_t GetSkillConstantDataCount(); + GWCA_API uint32_t GetSkillCount(); // Name/Description/Profession etc for an attribute by id GWCA_API AttributeInfo* GetAttributeConstantData(Constants::Attribute attribute_id); @@ -101,9 +97,7 @@ namespace GW { GWCA_API bool GetSkillTemplate(SkillTemplate& skill_template); } } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API int GetSkillSlot(uint32_t skill_id); GWCA_API bool UseSkill(uint32_t slot, uint32_t target); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/StoCMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/StoCMgr.h index 51b9f04b..a70391bd 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/StoCMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/StoCMgr.h @@ -5,10 +5,7 @@ namespace GW { - /* - StoC Manager - See https://github.com/GameRevision/GWLP-R/wiki/GStoC for some already explored packets. - */ + // StoC Manager. See https://github.com/GameRevision/GWLP-R/wiki/GStoC for some already explored packets. namespace Packet { namespace StoC { @@ -24,9 +21,7 @@ namespace GW { namespace StoC { typedef HookCallback PacketCallback; - // Register a function to be called when a packet is received. - // An altitude of 0 or less will be triggered before the packet is processed. - // An altitude greater than 0 will be triggered after the packet has been processed. + // Altitude <= 0 fires before the packet is processed, > 0 after it. GWCA_API bool RegisterPacketCallback( HookEntry *entry, uint32_t header, diff --git a/Dependencies/GWCA/Include/GWCA/Managers/TradeMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/TradeMgr.h index 05b3c973..3f52fe89 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/TradeMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/TradeMgr.h @@ -21,9 +21,7 @@ namespace GW { GWCA_API bool OfferItem(uint32_t item_id, uint32_t quantity = 0); }; } -// ============================================================ // C Interop API -// ============================================================ extern "C" { GWCA_API bool OpenTradeWindow(uint32_t agent_id); GWCA_API bool AcceptTrade(); diff --git a/Dependencies/GWCA/Include/GWCA/Managers/UIMgr.h b/Dependencies/GWCA/Include/GWCA/Managers/UIMgr.h index f34f0bf9..58628c56 100644 --- a/Dependencies/GWCA/Include/GWCA/Managers/UIMgr.h +++ b/Dependencies/GWCA/Include/GWCA/Managers/UIMgr.h @@ -148,7 +148,7 @@ namespace GW { uint32_t field15_0x3c; uint32_t field16_0x40; uint32_t field17_0x44; - uint32_t field18_0x48; + uint32_t m_ctlSpec; uint32_t field19_0x4c; uint32_t field20_0x50; uint32_t field21_0x54; @@ -236,10 +236,10 @@ namespace GW { struct AgentNameTagInfo { /* +h0000 */ uint32_t agent_id; - /* +h0004 */ uint32_t h0002; - /* +h0008 */ uint32_t h0003; + /* +h0004 */ uint32_t h0004; + /* +h0008 */ uint32_t h0008; /* +h000C */ wchar_t* name_enc; - /* +h0010 */ uint8_t h0010; + /* +h0010 */ uint8_t highlight; /* +h0011 */ uint8_t h0012; /* +h0012 */ uint8_t h0013; /* +h0013 */ uint8_t background_alpha; // ARGB, NB: Actual color is ignored, only alpha is used @@ -250,6 +250,8 @@ namespace GW { /* +h001E */ uint8_t h001E; /* +h001F */ uint8_t h001F; /* +h0020 */ wchar_t* extra_info_enc; // Title etc + /* +h0024 */ uint32_t extra_info_color; // ARGB + /* +h0028 */ uint32_t extra_info_attributes; // bold/size etc }; // Note: some windows are affected by UI scale (e.g. party members), others are not (e.g. compass) @@ -783,10 +785,35 @@ namespace GW { GWCA_API bool DestroyUIComponent(Frame* frame); - // Frame layout primitives (GW's Frame::SetBounds/SetPosition), for measuring and - // stacking child frames from a custom container's kMeasureContent/kSetLayout handler. - GWCA_API void SetFrameBounds(Frame* frame, uint32_t mode, float* rect, float* size_out); - GWCA_API void SetFramePosition(Frame* frame, uint32_t mode, float* rect); + // Bitfield accepted by Frame::SetBounds/SetPositionInternal + enum FrameLayoutMode : uint32_t { + FrameLayoutMode_None = 0, + FrameLayoutMode_AnchorBottom = 0x1, // vertical: anchor to the rect's bottom edge + FrameLayoutMode_Center = 0x2, // required alongside centered placement on either axis (asserts otherwise) + FrameLayoutMode_AnchorLeft = 0x4, // horizontal: anchor to the rect's left edge + FrameLayoutMode_AnchorRight = 0x8, // horizontal: anchor to the rect's right edge + FrameLayoutMode_AnchorTop = 0x10, // vertical: anchor to the rect's top edge + FrameLayoutMode_StretchWidth = 0x20, // horizontal: fill the rect's full width (also behaves as a left anchor) + FrameLayoutMode_StretchHeight = 0x40, // vertical: fill the rect's full height (also behaves as a bottom anchor) + FrameLayoutMode_AnchorHorizontalMargin = 0x80, // gate: trim the resolved rect inward on the AnchorLeft/AnchorRight edge(s) + FrameLayoutMode_AnchorVerticalMargin = 0x100, // gate: trim the resolved rect inward on the AnchorTop/AnchorBottom edge(s) + }; + inline FrameLayoutMode operator|(FrameLayoutMode a, FrameLayoutMode b) { + return static_cast(static_cast(a) | static_cast(b)); + } + inline FrameLayoutMode operator&(FrameLayoutMode a, FrameLayoutMode b) { + return static_cast(static_cast(a) & static_cast(b)); + } + inline FrameLayoutMode operator~(FrameLayoutMode a) { + return static_cast(~static_cast(a)); + } + + // Frame layout primitives (GW's Frame::SetBounds/SetPosition) for a container's kMeasureContent/kSetLayout handler. + GWCA_API void SetFrameBounds(Frame* frame, FrameLayoutMode mode, float* rect, float* size_out); + GWCA_API void SetFramePosition(Frame* frame, FrameLayoutMode mode, float* rect); + + // Appends an additional handler onto frame->frame_callbacks + GWCA_API bool AddFrameCallback(Frame* frame, UIInteractionCallback callback, void* uictl_context = nullptr); GWCA_API bool SelectDropdownOption(Frame* frame, uint32_t value); @@ -867,6 +894,7 @@ namespace GW { GWCA_API bool SetFrameVisible(UI::Frame* frame, bool flag); GWCA_API bool SetFrameDisabled(UI::Frame* frame, bool flag); + // Stubbed: always returns false. The underlying game function is not scanned for. GWCA_API bool AddFrameUIInteractionCallback(GW::UI::Frame*, UI::UIInteractionCallback callback, void* wparam); GWCA_API bool TriggerFrameRedraw(UI::Frame* frame); diff --git a/Dependencies/GWCA/Include/GWCA/Packets/StoC.h b/Dependencies/GWCA/Include/GWCA/Packets/StoC.h index 5ae5fdc2..9286bb4e 100644 --- a/Dependencies/GWCA/Include/GWCA/Packets/StoC.h +++ b/Dependencies/GWCA/Include/GWCA/Packets/StoC.h @@ -3,15 +3,7 @@ #include #include -/* -Server to client packets, sorted by header - -most packets are not filled in, however the list and types of -fields is given as comments - -feel free to fill packets, and you can also add a suffix to -the packet name, e.g. P391 -> P391_InstanceLoadMap -*/ +// Server to client packets, sorted by header. Unfilled ones list their fields as comments -- fill them in, optionally suffixing the name (P391 -> P391_InstanceLoadMap). namespace GW { typedef uint32_t AgentID; @@ -28,11 +20,7 @@ namespace GW { struct PacketBase { uint32_t header; }; - // Used for: - // GenericValue - // GenericValueTarget - // GenericModifier (i.e. GenericFloatTarget) - // GenericFloat + // Used for GenericValue, GenericValueTarget, GenericModifier (GenericFloatTarget) and GenericFloat. namespace GenericValueID { const uint32_t melee_attack_finished = 1; // GenericValue. Last melee attack finished successfully. const uint32_t attack_stopped = 3; // GenericValue. Last melee/ranged attack stopped unsuccessfully. May be followed by interrupted (35). @@ -82,18 +70,9 @@ namespace GW { } namespace JumboMessageValue { - // The following values represent the first and second parties in an explorable areas - // (inc. observer mode) with 2 parties. - // if there are 3 parties in the explorable area (like some HA maps) then: - // - party 1 = 6579558 - // - party 2 = 1635021873 - // - party 3 = 1635021874 - - // TODO: - // These numbers appear big and random, their origin or relation to other values - // is not understood. - // In addition, there may be a danger these variables could change with GW updates... - // Consider these values as experimental and use with caution + // First and second parties in an explorable area; with 3 parties (some HA maps) they are 6579558, 1635021873, 1635021874. + + // TODO: these numbers are big, random and not understood, and may change with GW updates -- use with caution. const uint32_t PARTY_ONE = 1635021873; const uint32_t PARTY_TWO = 1635021874; } @@ -456,12 +435,7 @@ namespace GW { }; template<> constexpr uint32_t Packet::STATIC_HEADER = GAME_SMSG_AGENT_ATTR_UPDATE_FLOAT; - // damage or healing done packet, but also has other purposes. - // to be investigated further. - // all types have their value in the float field 'value'. - // in all types the value is in percentage, unless otherwise specified. - // the value can be negative (e.g. damage, sacrifice) - // or positive (e.g. heal, lifesteal). + // Damage or healing done, plus other uses; 'value' is a percentage unless stated, negative or positive by type. struct GenericModifier : Packet { uint32_t type; // type as specified above in P156_Type uint32_t target_id; // agent id of who is affected by the change @@ -470,9 +444,7 @@ namespace GW { }; template<> constexpr uint32_t Packet::STATIC_HEADER = GAME_SMSG_AGENT_ATTR_UPDATE_FLOAT_TARGET; - // Projectile launched from an agent - // Can be from a martial weapon (spear, bow), or projectile-launching skill - // also acts as an attack_finished packet for ranged weapons, if `is_attack == 1` + // Projectile launched from an agent, martial or skill; also an attack_finished for ranged weapons when is_attack == 1. struct AgentProjectileLaunched : Packet { uint32_t agent_id; Vec2f destination; @@ -657,8 +629,7 @@ namespace GW { }; template<> constexpr uint32_t Packet::STATIC_HEADER = GAME_SMSG_TOWN_ALLIANCE_OBJECT; - // Info about any guilds applicable to current outpost. - // NOTE: When entering a guild hall, that guild will always be the first added (local_id = 1). + // Guilds applicable to the current outpost; entering a guild hall always adds that guild first (local_id = 1). struct GuildGeneral : Packet { uint32_t local_id; uint32_t ghkey[4]; // blob[16] @@ -770,17 +741,12 @@ namespace GW { }; template<> constexpr uint32_t Packet::STATIC_HEADER = GAME_SMSG_ITEM_SALVAGE_SESSION_SUCCESS; - // JumboMessage represents a message strewn across the center of the screen in big red or green characters. - // Things like moral boosts, flag captures, victory, defeat... + // A message strewn across the centre of the screen in big red or green characters -- moral boosts, captures, victory. struct JumboMessage : GW::Packet::StoC::Packet { uint8_t type; // JumboMessageType uint32_t value; // JumboMessageValue }; - // Diagnostic GCC/MinGW build: unlike the other specializations - // here, this one isn't constexpr (so isn't implicitly inline), - // causing an ODR multiple-definition link error across TUs that - // MSVC's linker tolerates but GCC's doesn't. Match the pattern - // used everywhere else in this file. + // Not constexpr unlike the others here, so GCC raises an ODR multiple-definition error MSVC tolerates -- match the pattern. template<> constexpr uint32_t GW::Packet::StoC::Packet::STATIC_HEADER = GAME_SMSG_JUMBO_MESSAGE; struct InstanceLoadFile : Packet { @@ -934,12 +900,7 @@ namespace GW { }; template<> constexpr uint32_t Packet::STATIC_HEADER = GAME_SMSG_PARTY_PLAYER_READY; - // When a new party is created: - // 1. PartyPlayerStreamStart packet is sent - // 2. PartyPlayerAdd packet per member - // PartyHeroAdd packet per hero - // PartyHenchmanAdd packer per henchman - // 3. PartyPlayerStreamEnd packet is sent + // New party: PartyPlayerStreamStart, then Add packets per player/hero/henchman, then PartyPlayerStreamEnd. struct PartyPlayerStreamStart : Packet { uint32_t party_id; // word }; diff --git a/Dependencies/GWCA/Include/GWCA/Utilities/Debug.h b/Dependencies/GWCA/Include/GWCA/Utilities/Debug.h index b587bd16..01513c52 100644 --- a/Dependencies/GWCA/Include/GWCA/Utilities/Debug.h +++ b/Dependencies/GWCA/Include/GWCA/Utilities/Debug.h @@ -1,9 +1,6 @@ #pragma once #ifdef _DEBUG - // Diagnostic GCC/MinGW build: plain __VA_ARGS__ leaves a trailing comma - // when a call site passes no extra args (e.g. GWCA_INFO("message")), - // which MSVC's preprocessor tolerates but GCC's doesn't. ##__VA_ARGS__ - // is a GNU extension that swallows the comma in that case. + // ##__VA_ARGS__ is a GNU extension swallowing the trailing comma when no extra args are passed; MSVC tolerates plain __VA_ARGS__. #define GWCA_TRACE(fmt, ...) GW::LogMessage(GW::LEVEL_TRACE, __FILE__, (unsigned)__LINE__, __FUNCTION__, fmt, ##__VA_ARGS__) #define GWCA_DEBUG(fmt, ...) GW::LogMessage(GW::LEVEL_DEBUG, __FILE__, (unsigned)__LINE__, __FUNCTION__, fmt, ##__VA_ARGS__) #define GWCA_INFO(fmt, ...) GW::LogMessage(GW::LEVEL_INFO, __FILE__, (unsigned)__LINE__, __FUNCTION__, fmt, ##__VA_ARGS__) diff --git a/Dependencies/GWCA/Include/GWCA/Utilities/Export.h b/Dependencies/GWCA/Include/GWCA/Utilities/Export.h index 7bc4324f..11456f88 100644 --- a/Dependencies/GWCA/Include/GWCA/Utilities/Export.h +++ b/Dependencies/GWCA/Include/GWCA/Utilities/Export.h @@ -1,6 +1,17 @@ #pragma once -#if defined(__clang__) || defined(__GNUC__) +#include + +// GWCA is 32-bit only: every struct offset here comes from a 32-bit client, so a 64-bit build would read the wrong fields. +static_assert(sizeof(void*) == 4, + "GWCA is 32-bit only (x86 or wasm32). Configure with -A Win32, " + "or emcmake for wasm."); + +#if !defined(_WIN32) +// Non-PE targets: dllexport is meaningless and only warns; wasm exports come from EXPORTED_FUNCTIONS (tools/gen_wasm_exports.py). +# define DllExport +# define DllImport +#elif defined(__clang__) || defined(__GNUC__) # define DllExport __attribute__((dllexport)) # define DllImport __attribute__((dllimport)) #elif defined(_MSC_VER) diff --git a/Dependencies/GWCA/Include/GWCA/Utilities/Hooker.h b/Dependencies/GWCA/Include/GWCA/Utilities/Hooker.h index 6e620540..25e56a86 100644 --- a/Dependencies/GWCA/Include/GWCA/Utilities/Hooker.h +++ b/Dependencies/GWCA/Include/GWCA/Utilities/Hooker.h @@ -2,19 +2,124 @@ #include +#include + namespace GW { namespace Hook { + + // A code address on x86, a function index on wasm; not void*, since function-pointer-to-void* is ill-formed and clang rejects it. + struct HookTarget { + uintptr_t value = 0; + constexpr HookTarget() = default; + constexpr HookTarget(decltype(nullptr)) {} + template HookTarget(T* p) + : value((uintptr_t)p) {} + constexpr explicit operator bool() const { return value != 0; } + }; + +#if GWCA_WASM + // The loader sizes the dispatch table with TableBytes and passes it in -- the no-arg form below has no wasm definition. + GWCA_API size_t TableBytes(uint32_t first_func, uint32_t func_count); + GWCA_API bool Initialize(void* memory, uint32_t first_func, uint32_t func_count); + + // Move one funcref between GWCA's table and the game's, for a callback written into a game data structure (see StoCMgr). + GWCA_API uint32_t PublishFuncref(HookTarget local); + GWCA_API uint32_t AdoptFuncref(uint32_t game_slot); + + // A scanned function made callable. A scan yields a tagged code offset, + // which is not a table index -- calling one directly traps with "table + // index is out of bounds". A hooked function gets a callable form for + // free as its trampoline; anything called directly and never hooked has + // to be converted here first. 0 if it does not resolve. + GWCA_API uintptr_t CallableFromScan(uintptr_t scan_result); +#else GWCA_API void Initialize(); + + // x86 scan results are already code addresses, so call sites can be unconditional. + inline uintptr_t CallableFromScan(uintptr_t scan_result) { return scan_result; } +#endif GWCA_API void Deinitialize(); - // static void EnqueueHook(HookBase* base); - // static void RemoveHook(HookBase* base); + // static void EnqueueHook(HookBase*); static void RemoveHook(HookBase*); + + GWCA_API void EnableHooks(HookTarget target = nullptr); + GWCA_API void DisableHooks(HookTarget target = nullptr); + +#if GWCA_WASM + // A trampoline is only valid for call_indirect FROM WHICHEVER MODULE'S + // OWN "table_adopt" import populated it -- every wasm module gets its + // own table (see gw_in_browser's inject.js TableManager, one instance + // per module), and CreateHook is code compiled into gwca.wasm. A mod + // calling it cross-module got its trampoline written into GWCA's own + // table, not the mod's; the mod's own call_indirect through it then + // traps "table index is out of bounds" -- silently, since CreateHook + // itself reports success (the adopt genuinely succeeded, just into + // the wrong table). Found hooking a wasm test mod's own model-loading + // experiment; every existing hook avoided this only because GWCA + // hooks itself, so the code that creates the trampoline and the code + // that later calls it are always the same module by construction. + // + // This parameter closes that hole: it is mandatory, not defaulted, so + // a mod cannot compile a call to CreateHook without deciding what to + // pass. Supply the SAME kind of import GWCA's own uses internally -- + // an `extern "C" __attribute__((import_module("env"), + // import_name("table_adopt"))) uint32_t table_adopt(uint32_t);` declared + // in the mod's own translation unit, so the loader resolves it against + // the mod's own TableManager. GWCA's own 44 internal call sites use + // GW::Hook::GwcaTableAdopt via the GWCA_CREATE_HOOK macro below rather + // than repeat this by hand. + typedef uint32_t (*TableAdoptFn)(uint32_t game_slot); + + // gwca's OWN table_adopt import, valid ONLY for a trampoline that will + // be called from code compiled into gwca.wasm. Passing this from a mod + // reproduces exactly the bug this parameter exists to prevent -- a mod + // must supply its own import instead (see above). + GWCA_API uint32_t GwcaTableAdopt(uint32_t game_slot); + + GWCA_API int CreateHook(void** target, HookTarget detour, void** trampoline, + TableAdoptFn caller_table_adopt); + + // Prefer this form. The void** overload erases both signatures, so a + // detour whose type differs from its target's compiles on either + // platform: x86 ignores the difference, and wasm's call_indirect traps + // at the first call -- in a map load, far from the declaration. Here the + // two must deduce the same T, so a mismatch is a compile error instead. + template + int CreateHook(T* target, T detour, T* trampoline, TableAdoptFn caller_table_adopt) + { + return CreateHook((void**)target, HookTarget(detour), (void**)trampoline, caller_table_adopt); + } + + // GWCA's own call sites: `GWCA_CREATE_HOOK(&Func, Detour, &Ret)`, no + // different from the pre-parameter shape -- this is what supplies + // GwcaTableAdopt on their behalf so 44 internal call sites did not + // each need editing by hand. Not for mod code: a mod including this + // header gets the real, mandatory-parameter CreateHook above, and + // reaching for this macro to sidestep that is exactly the mistake the + // parameter exists to prevent -- pass your own import, not this one. + #define GWCA_CREATE_HOOK(target, detour, trampoline) \ + GW::Hook::CreateHook(target, detour, trampoline, GW::Hook::GwcaTableAdopt) +#else + GWCA_API int CreateHook(void** target, HookTarget detour, void** trampoline); - GWCA_API void EnableHooks(void* target = NULL); - GWCA_API void DisableHooks(void* target = NULL); + // Prefer this form. The void** overload erases both signatures, so a + // detour whose type differs from its target's compiles on either + // platform: x86 ignores the difference, and wasm's call_indirect traps + // at the first call -- in a map load, far from the declaration. Here the + // two must deduce the same T, so a mismatch is a compile error instead. + template + int CreateHook(T* target, T detour, T* trampoline) + { + return CreateHook((void**)target, HookTarget(detour), (void**)trampoline); + } - GWCA_API int CreateHook(void** target, void* detour, void** trampoline); - GWCA_API void RemoveHook(void* target); + // x86 has no wasm-style cross-module trampoline trap, so this is just + // CreateHook -- same macro name on both platforms so GWCA's own 44 + // internal call sites do not need an #if. + #define GWCA_CREATE_HOOK(target, detour, trampoline) \ + GW::Hook::CreateHook(target, detour, trampoline) +#endif + GWCA_API void RemoveHook(HookTarget target); GWCA_API void EnterHook(); GWCA_API void LeaveHook(); diff --git a/Dependencies/GWCA/Include/GWCA/Utilities/Scanner.h b/Dependencies/GWCA/Include/GWCA/Utilities/Scanner.h index dc1b8888..37a0a966 100644 --- a/Dependencies/GWCA/Include/GWCA/Utilities/Scanner.h +++ b/Dependencies/GWCA/Include/GWCA/Utilities/Scanner.h @@ -2,6 +2,10 @@ #include "Export.h" +#include +#include +#include + namespace GW { enum ScannerSection : uint8_t { Section_TEXT = 0, @@ -13,9 +17,7 @@ namespace GW { uintptr_t start = 0; uintptr_t end = 0; }; - // class PatternScanner - // 32 bit pattern scanner for x86 programs. - // Credits to Zat & Midi12 @ unknowncheats.me for the functionality of this class. + // 32-bit pattern scanner for x86 programs. Credits to Zat & Midi12 @ unknowncheats.me. namespace Scanner { // Initializer to determine scan range. GWCA_API void Initialize(const char* moduleName = NULL); @@ -40,6 +42,10 @@ namespace GW { // Actual pattern finder. GWCA_API uintptr_t Find(const char* pattern, const char* mask = 0, int offset = 0, ScannerSection section = ScannerSection::Section_TEXT); + // The nth match of a pattern, 0-based: FindNth(p, m, 0, off) == Find(p, m, off). + // Use when a pattern is deliberately ambiguous and it is a later hit you want. + GWCA_API uintptr_t FindNth(const char* pattern, const char* mask, size_t nth, int offset = 0, ScannerSection section = ScannerSection::Section_TEXT); + GWCA_API void GetSectionAddressRange(ScannerSection section, uintptr_t* start = nullptr, uintptr_t* end = nullptr); // Check if current address is a valid pointer (usually to a data variable in DATA) @@ -49,5 +55,80 @@ namespace GW { GWCA_API uintptr_t FunctionFromNearCall(uintptr_t call_instruction_address, bool check_valid_ptr = true); GWCA_API uintptr_t ToFunctionStart(uintptr_t call_instruction_address, uint32_t scan_range = 0xff); + +#if GWCA_WASM + // Every scan returns an address, as on x86 -- here a tagged CODE OFFSET + // (0x80000000 | offset) rather than a linear one. FunctionAtCodeOffset is the one + // way back to a bare function index; ToFunctionStart and the arity checks take + // either form. See Source/Platform/Wasm/Scanner.cpp. + + // Initialise from the module bytes -- a running module cannot read its own code section. + GWCA_API bool Initialize(const void* wasm_bytes, size_t length); + GWCA_API bool IsInitialized(); + + // Scans with no wasm equivalent record what they were asked for rather than failing silently. + GWCA_API const std::vector& GetUnportedScans(); + + // Replaces *(uintptr_t*)address: LLVM puts the global in the load's memarg offset, not in a constant. + GWCA_API uintptr_t GlobalFromFunction(uintptr_t func_index, size_t nth = 0); + GWCA_API std::vector GlobalsInFunction(uintptr_t func_index); + + // The global read straight out of a load/store you already located. 0 if that offset is not a load/store. + GWCA_API uintptr_t GlobalAtCodeOffset(uintptr_t code_offset); + + // The i32.const immediate at an offset you already located, decoded from LEB128 -- + // what a deref does on x86, which cannot work here: the code section is not in + // linear memory, and the immediate is a varint rather than a raw word. + GWCA_API uintptr_t ConstAtCodeOffset(uintptr_t code_offset); + + // A static passed as an i32.const rather than loaded from; restricted to .bss to exclude .data string constants. + GWCA_API uintptr_t BssConstFromFunction(uintptr_t func_index, size_t nth = 0); + + // The one function referencing `str` with this exact type; 0 if none or ambiguous. + // For an overload pair a shared string cannot separate and an ordinal must not: nth + // orders by .text address on x86 but by function index here, and they disagree. + GWCA_API uintptr_t FindUseOfStringWithSignature(const char* str, uint32_t params, + uint32_t results); + + // Replaces the Find+FunctionFromNearCall idiom: find where a constant is passed, take the callee. + GWCA_API uintptr_t FindCallAfterConst(int32_t value, int window = 6); + GWCA_API uintptr_t FindCallAfterConstWithArity(int32_t value, uint32_t nparams, + int window = 6); + + // Does the resolved index take `expected` wasm params? True when it matches or cannot be determined; drive it via the macro. + GWCA_API bool CheckArity(uintptr_t func_index, uint32_t expected, const char* what); + + // Same, plus the RESULT count -- the only guard catching the return-type mismatch that makes call_indirect trap. + GWCA_API bool CheckSignature(uintptr_t func_index, uint32_t expected_params, + uint32_t expected_results, const char* what); + + // Resolve a code offset or bare index to a function index (0 if it does not resolve); GW::Hook uses it to find what it hooks. + GWCA_API uintptr_t FunctionAtCodeOffset(uintptr_t address); + + // The function's start as a CODE OFFSET, which is what ToFunctionStart + // used to give. Only for scanning on from it -- FindInRange bounds and + // address arithmetic. ToFunctionStart now returns something callable, + // and an offset is not callable any more than a callable is scannable. + GWCA_API uintptr_t FunctionStartAddress(uintptr_t address, uint32_t scan_range = 0); + + // Records that `callable` is `func_index`, so a scan result that has been + // made callable still resolves for CheckArity, GlobalFromFunction and + // CreateHook. GW::Hook fills this in as it adopts. + GWCA_API void NoteCallable(uintptr_t callable, uintptr_t func_index); +#endif } } + +// Discard a scan result whose resolved function takes the wrong wasm parameter count. No-op on x86. +#if GWCA_WASM +// Discards with `= 0` not `= nullptr`, so it takes a raw uintptr_t scan result as well as a typed function pointer. +#define GWCA_SCAN_CHECK_ARITY(func, expected) \ + do { if (!GW::Scanner::CheckArity((uintptr_t)(func), (uint32_t)(expected), #func)) (func) = 0; } while (0) +// Prefer over GWCA_SCAN_CHECK_ARITY when the result count is known: 0 for void, 1 otherwise. +#define GWCA_SCAN_CHECK_SIG(func, params, results) \ + do { if (!GW::Scanner::CheckSignature((uintptr_t)(func), (uint32_t)(params), \ + (uint32_t)(results), #func)) (func) = 0; } while (0) +#else +#define GWCA_SCAN_CHECK_ARITY(func, expected) ((void)0) +#define GWCA_SCAN_CHECK_SIG(func, params, results) ((void)0) +#endif diff --git a/Dependencies/GWCA/Include/GWCA/Utilities/Version.h b/Dependencies/GWCA/Include/GWCA/Utilities/Version.h new file mode 100644 index 00000000..1033026b --- /dev/null +++ b/Dependencies/GWCA/Include/GWCA/Utilities/Version.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +// Guards against headers and binary being from different releases -- struct offsets shift and nothing else catches it. +#define GWCA_ABI_VERSION 0x04080500u + +extern "C" { + // The version the binary was built at, against GWCA_ABI_VERSION which is what the caller compiled against. + GWCA_API uint32_t gwca_abi_version(void); +} + +namespace GW { + // Call before anything else: a mismatch means every struct offset is suspect. + inline bool AbiVersionMatches() + { + return gwca_abi_version() == GWCA_ABI_VERSION; + } +} diff --git a/Dependencies/GWCA/Include/GWCA/stdafx.h b/Dependencies/GWCA/Include/GWCA/stdafx.h index c988dfa3..67bd6916 100644 --- a/Dependencies/GWCA/Include/GWCA/stdafx.h +++ b/Dependencies/GWCA/Include/GWCA/stdafx.h @@ -1,5 +1,7 @@ #pragma once +// MSVC-only: every other compiler reports the whole block as unknown-pragma noise. +#ifdef _MSC_VER #pragma warning(disable: 4619) // there is no warning number 'number' #pragma warning(push) @@ -20,6 +22,7 @@ #pragma warning(disable: 5027) // 'type': move assignment operator was implicitly defined as deleted #pragma warning(disable: 5039) // 'function': pointer or reference to potentially throwing function passed to extern C function under -EHc. Undefined behavior may occur if this function throws an exception. #pragma warning(disable: 5045) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +#endif #ifndef _CRT_SECURE_NO_WARNINGS # define _CRT_SECURE_NO_WARNINGS 1 @@ -49,10 +52,18 @@ # define WIN32_LEAN_AND_MEAN #endif +// GWCA_WASM comes from the build, not a header, so an undefined macro takes the Win32 branch -- what a consumer wants. +#if GWCA_WASM +// No Win32 here: the shim keeps the few Windows typedefs in GWCA's public headers parsing, and does not ship. +#include +#else #include #include +#endif +#ifdef _MSC_VER #pragma warning(pop) +#endif #ifndef _MSC_VER // GCC/MinGW diagnostic-build compatibility shim (not needed on MSVC). @@ -60,6 +71,7 @@ #endif +#ifdef _MSC_VER #pragma warning(disable: 4061) // enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label #pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union #pragma warning(disable: 4514) // 'function' : unreferenced inline function has been removed @@ -77,3 +89,4 @@ #pragma warning(disable: 5027) // 'type': move assignment operator was implicitly defined as deleted #pragma warning(disable: 5045) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified #pragma warning(disable: 28159) // Consider using 'GetTickCount64' instead of 'GetTickCount'. +#endif diff --git a/Dependencies/GWCA/gwca.dll b/Dependencies/GWCA/gwca.dll index f8b5a571..022576c0 100644 Binary files a/Dependencies/GWCA/gwca.dll and b/Dependencies/GWCA/gwca.dll differ diff --git a/Dependencies/GWCA/gwca.exp b/Dependencies/GWCA/gwca.exp index 8045c53d..8e36a7ba 100644 Binary files a/Dependencies/GWCA/gwca.exp and b/Dependencies/GWCA/gwca.exp differ diff --git a/Dependencies/GWCA/gwca.lib b/Dependencies/GWCA/gwca.lib index e9ff920f..41154097 100644 Binary files a/Dependencies/GWCA/gwca.lib and b/Dependencies/GWCA/gwca.lib differ diff --git a/Directory.Build.props b/Directory.Build.props index a25b0ee3..ea279420 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ $(NoWarn);IL2104;IL3053;IL3000;IL3002;NU1701;CS0108 - 0.9.10.17 + 0.9.10.18 enable enable diff --git a/Directory.Packages.props b/Directory.Packages.props index d9e792dc..a5a7f853 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,40 +7,40 @@ - + - - - - + + + + - - - - - - - - - + + + + + + + + + - - + + - + - - - - - + + + + + @@ -54,14 +54,14 @@ - - - + + + - - - + + + diff --git a/LICENSE.md b/LICENSE.md index 367794c6..8fdd7c66 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,20 @@ MIT License -Copyright (c) 2025 Macocian Alexandru Victor +Copyright (c) 2026 Macocian Alexandru Victor -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index cdcdb363..5678d29d 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,34 @@ public static string ToWinePath(string linuxPath) } ``` +### Wine Debug Log + +Debug builds append all Wine output to `wine-debug.log` in the Daybreak output folder: + +``` +Daybreak.Linux/bin/Debug/net10.0/linux-x64/wine-debug.log +``` + +This is the place to look when Guild Wars dies without an error. Guild Wars is a grandchild of the Wine process Daybreak starts, so its stderr would otherwise go to a pipe nobody reads and the crash report would be lost. Wine runs `winedbg --auto` on a crash, so the log contains the faulting stack — including managed frames when `Daybreak.API` is at fault: + +``` +err:eventlog:ReportEventW L"Message: Access Violation: Attempted to read or write protected memory..." +err:eventlog:ReportEventW L" at Daybreak.API.Interop.GuildWars.GuildWarsArray`1.Enumerator.get_Current()" +err:eventlog:ReportEventW L" at Daybreak.API.Services.CharacterSelectService... +err:seh:NtRaiseException Unhandled exception code c0000409 +``` + +Note that `Daybreak.API` is NativeAOT, which cannot throw `AccessViolationException`. A bad pointer read therefore calls `FailFast` and terminates Guild Wars instantly, with nothing written to `Daybreak.API.log`. + +Each launch is delimited by a `===== =====` header. The log is append-only, so delete it when it gets large. + +Environment variables: + +| Variable | Effect | +|----------|--------| +| `DAYBREAK_WINE_DEBUG` | `1` forces logging on, `0` off. Defaults to on for Debug builds, off for Release. | +| `DAYBREAK_WINE_DEBUG_CHANNELS` | Sets `WINEDEBUG`, e.g. `+seh`. Unset by default — Wine's default `err` class already reports crashes, and trace channels slow the game badly. | + --- ## Credits diff --git a/Scripts/SetupWinePrefix.sh b/Scripts/SetupWinePrefix.sh index 8cf8ffc5..f479c614 100755 --- a/Scripts/SetupWinePrefix.sh +++ b/Scripts/SetupWinePrefix.sh @@ -19,6 +19,15 @@ set -euo pipefail # ─── configuration ──────────────────────────────────────────────────────────── DOTNET_SDK_CHANNEL="${DOTNET_SDK_CHANNEL:-10.0}" +# Pinned, not "latest": the SDK's bundled Roslyn must be >= the +# Microsoft.CodeAnalysis.CSharp version referenced by Daybreak.Generators +# (see Directory.Packages.props). A generator built against a newer Roslyn is +# rejected with CS9057 and silently dropped, which makes Daybreak.API fail to +# compile with a wall of "GWCAEquivalentAttribute could not be found" errors. +# SDK 10.0.300 -> Roslyn 5.6.0.0 +# SDK 10.0.400 -> Roslyn 5.9.0.0 +# Set DOTNET_SDK_VERSION=latest to track the newest SDK in the channel. +DOTNET_SDK_VERSION="${DOTNET_SDK_VERSION:-10.0.400}" LLVM_VERSION="${LLVM_VERSION:-22.1.5}" XWIN_VERSION="${XWIN_VERSION:-0.9.0}" WINEPREFIX_DEFAULT="$HOME/.wine-daybreak" @@ -68,21 +77,28 @@ fi # ─── 2. .NET SDK ────────────────────────────────────────────────────────────── DOTNET_DIR="$WINEPREFIX/drive_c/dotnet" -if [[ ! -f "$DOTNET_DIR/dotnet.exe" ]]; then - log "Resolving latest .NET $DOTNET_SDK_CHANNEL SDK" - SDK_INFO=$(curl -fsSL "https://builds.dotnet.microsoft.com/dotnet/release-metadata/${DOTNET_SDK_CHANNEL}/releases.json" | python3 -c " +log "Resolving .NET SDK ($DOTNET_SDK_VERSION) from channel $DOTNET_SDK_CHANNEL" +SDK_INFO=$(curl -fsSL "https://builds.dotnet.microsoft.com/dotnet/release-metadata/${DOTNET_SDK_CHANNEL}/releases.json" | python3 -c " import json,sys +want=sys.argv[1] d=json.load(sys.stdin) -v=d['latest-sdk'] +if want=='latest': + want=d['latest-sdk'] for r in d['releases']: - if r['sdk']['version']==v: - for f in r['sdk']['files']: + for s in ([r['sdk']] + r.get('sdks',[])): + if s['version']!=want: + continue + for f in s['files']: if f['rid']=='win-x64' and f['name'].endswith('.zip'): - print(v); print(f['url']); sys.exit(0) -sys.exit('No win-x64 SDK zip found') -") - SDK_VER=$(echo "$SDK_INFO" | sed -n '1p') - SDK_URL=$(echo "$SDK_INFO" | sed -n '2p') + print(want); print(f['url']); sys.exit(0) +sys.exit(f'No win-x64 SDK zip found for {want}') +" "$DOTNET_SDK_VERSION") +SDK_VER=$(echo "$SDK_INFO" | sed -n '1p') +SDK_URL=$(echo "$SDK_INFO" | sed -n '2p') + +# Keyed on the versioned SDK directory, not dotnet.exe: bumping +# DOTNET_SDK_VERSION must actually install the new SDK into an existing prefix. +if [[ ! -d "$DOTNET_DIR/sdk/$SDK_VER" ]]; then SDK_ZIP="$CACHE_DIR/dotnet-sdk-${SDK_VER}-win-x64.zip" if [[ ! -f "$SDK_ZIP" ]]; then @@ -94,10 +110,12 @@ sys.exit('No win-x64 SDK zip found') fi mkdir -p "$DOTNET_DIR" - log "Extracting SDK into $DOTNET_DIR" - (cd "$DOTNET_DIR" && unzip -q "$SDK_ZIP") + # -o so a newer SDK lands side-by-side and refreshes the shared host/runtime. + # With no global.json in the repo, dotnet then selects the highest SDK. + log "Extracting SDK $SDK_VER into $DOTNET_DIR" + (cd "$DOTNET_DIR" && unzip -qo "$SDK_ZIP") else - log ".NET SDK already present in $DOTNET_DIR" + log ".NET SDK $SDK_VER already present in $DOTNET_DIR" fi # ─── 3. LLVM Windows binaries (lld-link.exe, llvm-lib.exe) ─────────────────── diff --git a/Tools/SkillUpdater/WikiSkillParser.cs b/Tools/SkillUpdater/WikiSkillParser.cs index 369b1cbb..0401b2f5 100644 --- a/Tools/SkillUpdater/WikiSkillParser.cs +++ b/Tools/SkillUpdater/WikiSkillParser.cs @@ -194,9 +194,13 @@ private static Dictionary ExtractFields(string body) { var c = body[i]; if (c == '{' && i + 1 < body.Length && body[i + 1] == '{') { braceDepth++; i += 2; continue; } + if (c == '}' && i + 1 < body.Length && body[i + 1] == '}') { braceDepth--; i += 2; continue; } + if (c == '[' && i + 1 < body.Length && body[i + 1] == '[') { bracketDepth++; i += 2; continue; } + if (c == ']' && i + 1 < body.Length && body[i + 1] == ']') { bracketDepth--; i += 2; continue; } + if (c == '|' && braceDepth == 0 && bracketDepth == 0) { break;