From 8a0e4bb481f1c3dc5aa89a27f3221ae5f11bf7f0 Mon Sep 17 00:00:00 2001 From: Boki Date: Wed, 4 Mar 2026 15:36:20 -0500 Subject: [PATCH] skills working somewhat --- offsets.json | 117 +- src/Automata.Ui/Automata.Ui.csproj | 4 + src/Automata.Ui/ViewModels/MemoryViewModel.cs | 175 +- src/Automata.Ui/Views/MainWindow.axaml | 6 + src/Roboto.Core/QuestProgress.cs | 7 + src/Roboto.Core/SkillState.cs | 18 +- src/Roboto.Data/MemoryPoller.cs | 38 +- src/Roboto.GameOffsets/Components/Actor.cs | 18 +- src/Roboto.Memory/GameMemoryReader.cs | 28 +- src/Roboto.Memory/GameOffsets.cs | 149 +- src/Roboto.Memory/MemoryDiagnostics.cs | 1453 ++++++++++++++++- src/Roboto.Memory/QuestNameLookup.cs | 67 + src/Roboto.Memory/QuestReader.cs | 201 ++- src/Roboto.Memory/SkillReader.cs | 74 +- src/Roboto.Systems/CombatSystem.cs | 7 +- tools/MemoryViewer/MemoryViewer.cs | 506 ++++++ tools/MemoryViewer/MemoryViewer.csproj | 29 + tools/MemoryViewer/MemoryViewerSettings.cs | 23 + tools/PatternTool/PatternTool.cs | 621 +++++++ tools/PatternTool/PatternTool.csproj | 32 + tools/PatternTool/PatternToolSettings.cs | 24 + tools/skillbar_scanner.py | 791 +++++++++ 22 files changed, 4227 insertions(+), 161 deletions(-) create mode 100644 src/Roboto.Memory/QuestNameLookup.cs create mode 100644 tools/MemoryViewer/MemoryViewer.cs create mode 100644 tools/MemoryViewer/MemoryViewer.csproj create mode 100644 tools/MemoryViewer/MemoryViewerSettings.cs create mode 100644 tools/PatternTool/PatternTool.cs create mode 100644 tools/PatternTool/PatternTool.csproj create mode 100644 tools/PatternTool/PatternToolSettings.cs create mode 100644 tools/skillbar_scanner.py diff --git a/offsets.json b/offsets.json index 4d6677b..2c1be35 100644 --- a/offsets.json +++ b/offsets.json @@ -2,62 +2,97 @@ "ProcessName": "PathOfExileSteam", "GameStatePattern": "48 83 EC ?? 48 8B F1 33 ED 48 39 2D ^", "GameStateGlobalOffset": 0, - "PatternResultAdjust": 24, - "StatesBeginOffset": 72, - "StateStride": 16, + "PatternResultAdjust": "0x18", + "StatesBeginOffset": "0x48", + "StateStride": "0x10", "StatePointerOffset": 0, "StateCount": 12, "InGameStateIndex": 4, - "ActiveStatesOffset": 32, + "ActiveStatesOffset": "0x20", "StatesInline": true, - "InGameStateDirectOffset": 528, - "IsLoadingOffset": 832, - "EscapeStateOffset": 524, - "IngameDataFromStateOffset": 656, - "WorldDataFromStateOffset": 760, - "AreaLevelOffset": 196, + "InGameStateDirectOffset": "0x210", + "IsLoadingOffset": "0x340", + "EscapeStateOffset": "0x20C", + "IngameDataFromStateOffset": "0x290", + "WorldDataFromStateOffset": "0x2F8", + "AreaLevelOffset": "0xC4", "AreaLevelIsByte": true, "AreaLevelStaticOffset": 0, - "AreaHashOffset": 236, - "ServerDataOffset": 2544, - "LocalPlayerDirectOffset": 2576, - "EntityListOffset": 2896, + "AreaHashOffset": "0xEC", + "ServerDataOffset": "0xA08", + "LocalPlayerDirectOffset": "0xA10", + "EntityListOffset": "0xB50", "EntityCountInternalOffset": 8, "EntityNodeLeftOffset": 0, "EntityNodeParentOffset": 8, - "EntityNodeRightOffset": 16, - "EntityNodeValueOffset": 40, - "EntityIdOffset": 128, - "EntityFlagsOffset": 132, + "EntityNodeRightOffset": "0x10", + "EntityNodeValueOffset": "0x28", + "EntityIdOffset": "0x80", + "EntityFlagsOffset": "0x84", "EntityDetailsOffset": 8, "EntityPathStringOffset": 8, - "LocalPlayerOffset": 32, - "ComponentListOffset": 16, + "LocalPlayerOffset": "0x20", + "PlayerServerDataOffset": "0x48", + "QuestFlagsOffset": "0x308", + "QuestFlagEntrySize": 4, + "QuestEntryQuestPtrOffset": 0, + "QuestEntryStateIdOffset": 0, + "QuestEntryStateTextOffset": 0, + "QuestEntryProgressTextOffset": 0, + "QuestFlagsContainerType": "int_vector", + "QuestFlagsMaxEntries": "0x100", + "SkillBarIdsOffset": "0x71A8", + "QuestCountOffset": "0x250", + "QuestCompanionOffset": "0x18", + "QuestCompanionEntrySize": "0x18", + "QuestCompanionDatPtrOffset": "0x10", + "QuestCompanionTrackedOffset": 4, + "QuestCompanionObjPtrOffset": "0x10", + "QuestTrackedMarker": "0x43020000", + "QuestObjEncounterStateOffset": 8, + "QuestDatRowSize": "0x77", + "QuestDatNameOffset": 0, + "QuestDatInternalIdOffset": "0x6B", + "QuestDatActOffset": "0x73", + "ComponentListOffset": "0x10", "EntityHeaderOffset": 8, - "ComponentLookupOffset": 40, - "ComponentLookupVec2Offset": 40, - "ComponentLookupEntrySize": 16, + "ComponentLookupOffset": "0x28", + "ComponentLookupVec2Offset": "0x28", + "ComponentLookupEntrySize": "0x10", "ComponentLookupNameOffset": 0, "ComponentLookupIndexOffset": 8, "LifeComponentIndex": -1, "RenderComponentIndex": -1, - "LifeComponentOffset1": 1056, - "LifeComponentOffset2": 152, - "LifeHealthOffset": 424, - "LifeManaOffset": 504, - "LifeEsOffset": 560, - "VitalCurrentOffset": 48, - "VitalTotalOffset": 44, - "PositionXOffset": 312, - "PositionYOffset": 316, - "PositionZOffset": 320, - "CameraOffset": 776, - "CameraMatrixOffset": 416, - "TerrainListOffset": 3264, + "LifeComponentOffset1": "0x420", + "LifeComponentOffset2": "0x98", + "LifeHealthOffset": "0x1A8", + "LifeManaOffset": "0x1F8", + "LifeEsOffset": "0x230", + "VitalCurrentOffset": "0x30", + "VitalTotalOffset": "0x2C", + "PositionXOffset": "0x138", + "PositionYOffset": "0x13C", + "PositionZOffset": "0x140", + "CameraOffset": "0x308", + "CameraMatrixOffset": "0x1A0", + "TerrainListOffset": "0xCC0", "TerrainInline": true, - "TerrainDimensionsOffset": 144, - "TerrainWalkableGridOffset": 328, - "TerrainBytesPerRowOffset": 424, + "TerrainDimensionsOffset": "0x90", + "TerrainWalkableGridOffset": "0x148", + "TerrainBytesPerRowOffset": "0x1A8", "TerrainGridPtrOffset": 8, - "SubTilesPerCell": 23 -} \ No newline at end of file + "SubTilesPerCell": "0x17", + "UiRootStructOffset": "0x340", + "UiRootPtrOffset": "0x5B8", + "GameUiPtrOffset": "0xBE0", + "GameUiControllerPtrOffset": "0xBE8", + "UiElementSelfOffset": 8, + "UiElementChildrenOffset": "0x10", + "UiElementParentOffset": "0xB8", + "UiElementStringIdOffset": "0x98", + "UiElementFlagsOffset": "0x180", + "UiElementVisibleBit": 11, + "UiElementSizeOffset": "0x288", + "UiElementTextOffset": "0x448", + "UiElementScanRange": "0x1000" +} diff --git a/src/Automata.Ui/Automata.Ui.csproj b/src/Automata.Ui/Automata.Ui.csproj index 3c9e2b8..ce866f4 100644 --- a/src/Automata.Ui/Automata.Ui.csproj +++ b/src/Automata.Ui/Automata.Ui.csproj @@ -27,6 +27,10 @@ + + + + diff --git a/src/Automata.Ui/ViewModels/MemoryViewModel.cs b/src/Automata.Ui/ViewModels/MemoryViewModel.cs index 9f21e9b..9345e10 100644 --- a/src/Automata.Ui/ViewModels/MemoryViewModel.cs +++ b/src/Automata.Ui/ViewModels/MemoryViewModel.cs @@ -102,6 +102,7 @@ public partial class MemoryViewModel : ObservableObject private MemoryNodeViewModel? _entityTypesNode; private MemoryNodeViewModel? _entityListNode; private MemoryNodeViewModel? _skillsNode; + private MemoryNodeViewModel? _questsNode; partial void OnIsEnabledChanged(bool value) { @@ -208,11 +209,13 @@ public partial class MemoryViewModel : ObservableObject _playerMana = new MemoryNodeViewModel("Mana:") { Value = "?", ValueColor = "#484f58" }; _playerEs = new MemoryNodeViewModel("ES:") { Value = "?", ValueColor = "#484f58" }; _skillsNode = new MemoryNodeViewModel("Skills") { IsExpanded = false }; + _questsNode = new MemoryNodeViewModel("Quests") { IsExpanded = false }; player.Children.Add(_playerPos); player.Children.Add(_playerLife); player.Children.Add(_playerMana); player.Children.Add(_playerEs); player.Children.Add(_skillsNode); + player.Children.Add(_questsNode); // Entities var entitiesGroup = new MemoryNodeViewModel("Entities"); @@ -454,12 +457,13 @@ public partial class MemoryViewModel : ObservableObject _playerEs!.Set("? (set LifeComponentIndex)", false); } - // Player skills + // Player skills — expandable nodes with full details if (_skillsNode is not null) { if (snap.PlayerSkills is { Count: > 0 }) { - _skillsNode.Value = $"{snap.PlayerSkills.Count} skills"; + var onBar = snap.PlayerSkills.Count(s => s.SkillBarSlot >= 0); + _skillsNode.Value = $"{snap.PlayerSkills.Count} skills ({onBar} on bar)"; _skillsNode.ValueColor = "#3fb950"; while (_skillsNode.Children.Count > snap.PlayerSkills.Count) @@ -469,31 +473,81 @@ public partial class MemoryViewModel : ObservableObject { var skill = snap.PlayerSkills[i]; var name = skill.Name ?? $"Skill#{i}"; - var label = $"[{i}] {name}:"; - - var parts = new List(); - parts.Add(skill.CanBeUsed ? "Ready" : "Cooldown"); - if (skill.UseStage != 0) - parts.Add($"stage:{skill.UseStage}"); - if (skill.CooldownTimeMs > 0) - parts.Add($"cd:{skill.CooldownTimeMs}ms"); - if (skill.MaxUses > 1) - parts.Add($"charges:{skill.MaxUses - skill.ActiveCooldowns}/{skill.MaxUses}"); - parts.Add($"cast:{skill.CastType}"); - - var value = string.Join(" ", parts); + var slotTag = skill.SkillBarSlot >= 0 ? $"bar:{skill.SkillBarSlot}" : "off-bar"; + var summary = skill.CanBeUsed ? "Ready" : "Cooldown"; var color = skill.CanBeUsed ? "#3fb950" : "#d29922"; + // Build detail fields + var detailsList = new List<(string Label, string Value)> + { + ("Address", $"0x{skill.Address:X}"), + ("InternalName", skill.InternalName ?? "—"), + ("Id", $"0x{skill.Id:X4}"), + ("Id2", $"0x{skill.Id2:X4}"), + ("SkillBarSlot", skill.SkillBarSlot >= 0 ? skill.SkillBarSlot.ToString() : "-1"), + ("CanBeUsed", skill.CanBeUsed.ToString()), + ("UseStage", skill.UseStage.ToString()), + ("CastType", skill.CastType.ToString()), + ("CooldownTimeMs", skill.CooldownTimeMs > 0 ? $"{skill.CooldownTimeMs}ms" : "0"), + ("MaxUses", skill.MaxUses.ToString()), + ("ActiveCooldowns", skill.ActiveCooldowns.ToString()), + ("TotalUses", skill.TotalUses.ToString()), + }; + + // Add raw hex dump rows (16 bytes per line, from true object base) + if (skill.RawBytes is { Length: > 0 }) + { + detailsList.Add(("———", $"Raw dump (base=0x{skill.Address:X})")); + for (var off = 0; off < skill.RawBytes.Length; off += 16) + { + var len = Math.Min(16, skill.RawBytes.Length - off); + var hex = BitConverter.ToString(skill.RawBytes, off, len).Replace('-', ' '); + var i32 = off + 4 <= skill.RawBytes.Length + ? BitConverter.ToInt32(skill.RawBytes, off) : 0; + var i64 = off + 8 <= skill.RawBytes.Length + ? BitConverter.ToInt64(skill.RawBytes, off) : 0L; + var interp = $"i32={i32} i64=0x{i64:X}"; + detailsList.Add(($"+0x{off:X2}", $"{hex} ({interp})")); + } + } + + var details = detailsList.ToArray(); + + MemoryNodeViewModel skillNode; if (i < _skillsNode.Children.Count) { - _skillsNode.Children[i].Name = label; - _skillsNode.Children[i].Value = value; - _skillsNode.Children[i].ValueColor = color; + skillNode = _skillsNode.Children[i]; + skillNode.Name = $"{name} ({slotTag})"; + skillNode.Value = summary; + skillNode.ValueColor = color; } else { - var node = new MemoryNodeViewModel(label) { Value = value, ValueColor = color }; - _skillsNode.Children.Add(node); + skillNode = new MemoryNodeViewModel($"{name} ({slotTag})") + { + Value = summary, ValueColor = color, IsExpanded = false + }; + _skillsNode.Children.Add(skillNode); + } + + // Update detail children + while (skillNode.Children.Count > details.Length) + skillNode.Children.RemoveAt(skillNode.Children.Count - 1); + + for (var d = 0; d < details.Length; d++) + { + var (label, val) = details[d]; + if (d < skillNode.Children.Count) + { + skillNode.Children[d].Name = $"{label}:"; + skillNode.Children[d].Value = val; + skillNode.Children[d].ValueColor = "#8b949e"; + } + else + { + skillNode.Children.Add(new MemoryNodeViewModel($"{label}:") + { Value = val, ValueColor = "#8b949e" }); + } } } } @@ -505,6 +559,51 @@ public partial class MemoryViewModel : ObservableObject } } + // Quest states with rich info from companion vector + if (_questsNode is not null) + { + if (snap.QuestFlags is { Count: > 0 }) + { + var named = snap.QuestFlags.Count(q => q.QuestName is not null); + _questsNode.Value = $"{snap.QuestFlags.Count} quest states ({named} named)"; + _questsNode.ValueColor = "#3fb950"; + + while (_questsNode.Children.Count > snap.QuestFlags.Count) + _questsNode.Children.RemoveAt(_questsNode.Children.Count - 1); + + for (var i = 0; i < snap.QuestFlags.Count; i++) + { + var q = snap.QuestFlags[i]; + var trackedPrefix = q.IsTracked ? "[T] " : ""; + var stateLabel = q.StateId switch { 1 => "locked", 2 => "started", _ => $"s{q.StateId}" }; + var label = $"{trackedPrefix}{q.QuestName ?? (q.QuestStateIndex > 0 ? $"#{q.QuestStateIndex}" : $"[{i}]")}"; + var value = q.InternalId is not null + ? $"idx={q.QuestStateIndex} {stateLabel} id={q.InternalId}" + : $"idx={q.QuestStateIndex} {stateLabel}"; + + var color = q.IsTracked ? "#58a6ff" : q.StateId == 2 ? "#8b949e" : "#484f58"; + + if (i < _questsNode.Children.Count) + { + _questsNode.Children[i].Name = label; + _questsNode.Children[i].Value = value; + _questsNode.Children[i].ValueColor = color; + } + else + { + var node = new MemoryNodeViewModel(label) { Value = value, ValueColor = color }; + _questsNode.Children.Add(node); + } + } + } + else + { + _questsNode.Value = "—"; + _questsNode.ValueColor = "#484f58"; + _questsNode.Children.Clear(); + } + } + // Entities if (snap.Entities is { Count: > 0 }) { @@ -1220,4 +1319,40 @@ public partial class MemoryViewModel : ObservableObject ScanResult = _reader.Diagnostics!.ScanQuestFlags(); } + + [RelayCommand] + private void ScanUiElementsExecute() + { + if (_reader is null || !_reader.IsAttached) + { + ScanResult = "Error: not attached"; + return; + } + + ScanResult = _reader.Diagnostics!.ScanUiElements(); + } + + [RelayCommand] + private void ScanUiTextExecute() + { + if (_reader is null || !_reader.IsAttached) + { + ScanResult = "Error: not attached"; + return; + } + + ScanResult = _reader.Diagnostics!.ScanUiElementText(); + } + + [RelayCommand] + private void ScanQuestObjectsExecute() + { + if (_reader is null || !_reader.IsAttached) + { + ScanResult = "Error: not attached"; + return; + } + + ScanResult = _reader.Diagnostics!.ScanQuestStateObjects(); + } } diff --git a/src/Automata.Ui/Views/MainWindow.axaml b/src/Automata.Ui/Views/MainWindow.axaml index cba5cc5..5106860 100644 --- a/src/Automata.Ui/Views/MainWindow.axaml +++ b/src/Automata.Ui/Views/MainWindow.axaml @@ -778,6 +778,12 @@ Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" />