From 9bbb8460316abba0fd38eeaa355dda0296694db6 Mon Sep 17 00:00:00 2001 From: CarlPoppa Date: Thu, 16 Oct 2025 13:25:56 +0100 Subject: [PATCH 1/4] Add QuestInfo aob scan --- pkg/memory/offset.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/memory/offset.go b/pkg/memory/offset.go index 9f5be86..89924a1 100644 --- a/pkg/memory/offset.go +++ b/pkg/memory/offset.go @@ -17,6 +17,7 @@ type Offset struct { FPS uintptr KeyBindingsOffset uintptr KeyBindingsSkillsOffset uintptr + QuestInfo uintptr } func calculateOffsets(process *Process) Offset { @@ -84,6 +85,11 @@ func calculateOffsets(process *Process) Offset { relativeOffset := int32(binary.LittleEndian.Uint32(bytes)) keyBindingsSkillsOffset = uintptr(int64(pattern) + 15 + int64(relativeOffset)) + // QuestInfo + pattern = process.FindPattern(memory, "\x48\x8B\x0D\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x45\x33\xE4", "xxx????x????xxx") + questInfoOffsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32)) + questInfoOffset := pattern - process.moduleBaseAddressPtr + 7 + questInfoOffsetPtr + return Offset{ GameData: gameDataOffset, UnitTable: unitTableOffset, @@ -97,5 +103,6 @@ func calculateOffsets(process *Process) Offset { FPS: fpsOffset, KeyBindingsOffset: keyBindingsOffset, KeyBindingsSkillsOffset: keyBindingsSkillsOffset, + QuestInfo: questInfoOffset, } } From 4fea7bde76e15f855f17087f07b393f6ff5f2083 Mon Sep 17 00:00:00 2001 From: CarlPoppa Date: Thu, 16 Oct 2025 13:27:44 +0100 Subject: [PATCH 2/4] Deref pointers from questinfo aob to get questinfo --- pkg/memory/game_reader.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/memory/game_reader.go b/pkg/memory/game_reader.go index 700deb4..19a6271 100644 --- a/pkg/memory/game_reader.go +++ b/pkg/memory/game_reader.go @@ -94,11 +94,9 @@ func (gd *GameReader) GetData() data.Data { openMenus := gd.OpenMenus() // Quests - // q1 := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+0x22E2978, Uint64)) - // q2 := uintptr(gd.Process.ReadUInt(q1, Uint64)) - // q2 := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+0x22F1E79, Uint64)) - gameQuestsBytes := gd.Process.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x22D8369, 85) - // gameQuestsBytes = gameQuestsBytes[3:] + questDataPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.QuestInfo, Uint64)) + flagsBufferPtr := uintptr(gd.Process.ReadUInt(questDataPtr, Uint64)) + gameQuestsBytes := gd.Process.ReadBytesFromMemory(flagsBufferPtr, 82) d := data.Data{ Corpse: data.Corpse{ From ce746c57af8677f6ca6d80ab409a2f24eb55dc0c Mon Sep 17 00:00:00 2001 From: CarlPoppa Date: Thu, 16 Oct 2025 13:31:47 +0100 Subject: [PATCH 3/4] Refactor getQuests to read bits from 2 byte flags --- pkg/memory/quests.go | 100 ++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/pkg/memory/quests.go b/pkg/memory/quests.go index bde51f7..03ea37b 100644 --- a/pkg/memory/quests.go +++ b/pkg/memory/quests.go @@ -7,60 +7,70 @@ import ( func (gd *GameReader) getQuests(questBytes []byte) quest.Quests { return quest.Quests{ // Act 1 - quest.Act1DenOfEvil: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 0, 1)), - quest.Act1SistersBurialGrounds: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 1, 1)), - quest.Act1ToolsOfTheTrade: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 2, 1)), - quest.Act1TheSearchForCain: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 3, 1)), - quest.Act1TheForgottenTower: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 4, 1)), - quest.Act1SistersToTheSlaughter: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 5, 1)), + quest.Act1DenOfEvil: gd.getQuestStatus(gd.readQuestFlags(questBytes, 1)), + quest.Act1SistersBurialGrounds: gd.getQuestStatus(gd.readQuestFlags(questBytes, 2)), + quest.Act1ToolsOfTheTrade: gd.getQuestStatus(gd.readQuestFlags(questBytes, 3)), + quest.Act1TheSearchForCain: gd.getQuestStatus(gd.readQuestFlags(questBytes, 4)), + quest.Act1TheForgottenTower: gd.getQuestStatus(gd.readQuestFlags(questBytes, 5)), + quest.Act1SistersToTheSlaughter: gd.getQuestStatus(gd.readQuestFlags(questBytes, 6)), // Act 2 - quest.Act2RadamentsLair: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 8, 1)), - quest.Act2TheHoradricStaff: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 9, 1)), - quest.Act2TaintedSun: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 10, 1)), - quest.Act2ArcaneSanctuary: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 11, 1)), - quest.Act2TheSummoner: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 12, 1)), - quest.Act2TheSevenTombs: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 13, 1)), + quest.Act2RadamentsLair: gd.getQuestStatus(gd.readQuestFlags(questBytes, 9)), + quest.Act2TheHoradricStaff: gd.getQuestStatus(gd.readQuestFlags(questBytes, 10)), + quest.Act2TaintedSun: gd.getQuestStatus(gd.readQuestFlags(questBytes, 11)), + quest.Act2ArcaneSanctuary: gd.getQuestStatus(gd.readQuestFlags(questBytes, 12)), + quest.Act2TheSummoner: gd.getQuestStatus(gd.readQuestFlags(questBytes, 13)), + quest.Act2TheSevenTombs: gd.getQuestStatus(gd.readQuestFlags(questBytes, 14)), // Act 3 - quest.Act3LamEsensTome: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 16, 1)), - quest.Act3KhalimsWill: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 17, 1)), - quest.Act3BladeOfTheOldReligion: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 18, 1)), - quest.Act3TheGoldenBird: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 19, 1)), - quest.Act3TheBlackenedTemple: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 20, 1)), - quest.Act3TheGuardian: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 21, 1)), + quest.Act3LamEsensTome: gd.getQuestStatus(gd.readQuestFlags(questBytes, 17)), + quest.Act3KhalimsWill: gd.getQuestStatus(gd.readQuestFlags(questBytes, 18)), + quest.Act3BladeOfTheOldReligion: gd.getQuestStatus(gd.readQuestFlags(questBytes, 19)), + quest.Act3TheGoldenBird: gd.getQuestStatus(gd.readQuestFlags(questBytes, 20)), + quest.Act3TheBlackenedTemple: gd.getQuestStatus(gd.readQuestFlags(questBytes, 21)), + quest.Act3TheGuardian: gd.getQuestStatus(gd.readQuestFlags(questBytes, 22)), // Act 4 - quest.Act4TheFallenAngel: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 24, 1)), - quest.Act4TerrorsEnd: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 25, 1)), - quest.Act4HellForge: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 26, 1)), + quest.Act4TheFallenAngel: gd.getQuestStatus(gd.readQuestFlags(questBytes, 25)), + quest.Act4HellForge: gd.getQuestStatus(gd.readQuestFlags(questBytes, 27)), + quest.Act4TerrorsEnd: gd.getQuestStatus(gd.readQuestFlags(questBytes, 26)), // Act 5 - quest.Act5SiegeOnHarrogath: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 34, 1)), - quest.Act5RescueOnMountArreat: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 35, 1)), - quest.Act5PrisonOfIce: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 36, 1)), - quest.Act5BetrayalOfHarrogath: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 37, 1)), - quest.Act5RiteOfPassage: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 38, 1)), - quest.Act5EveOfDestruction: gd.getQuestStatus(ReadUIntFromBuffer(questBytes, 39, 1)), + quest.Act5SiegeOnHarrogath: gd.getQuestStatus(gd.readQuestFlags(questBytes, 35)), + quest.Act5RescueOnMountArreat: gd.getQuestStatus(gd.readQuestFlags(questBytes, 36)), + quest.Act5PrisonOfIce: gd.getQuestStatus(gd.readQuestFlags(questBytes, 37)), + quest.Act5BetrayalOfHarrogath: gd.getQuestStatus(gd.readQuestFlags(questBytes, 38)), + quest.Act5RiteOfPassage: gd.getQuestStatus(gd.readQuestFlags(questBytes, 39)), + quest.Act5EveOfDestruction: gd.getQuestStatus(gd.readQuestFlags(questBytes, 40)), } } -func (gd *GameReader) getQuestStatus(questStatusInt uint) []quest.Status { - var activeStates []quest.Status - activeStates = append(activeStates, quest.Status(questStatusInt)) - return activeStates + +func (gd *GameReader) readQuestFlags(questBytes []byte, questIndex int) uint16 { + byteOffset := 2 * questIndex + if byteOffset+1 >= len(questBytes) { + return 0 + } + return uint16(questBytes[byteOffset]) | (uint16(questBytes[byteOffset+1]) << 8) } -//outdated +func (gd *GameReader) getQuestStatus(flags uint16) []quest.Status { + var statuses []quest.Status -/* func (gd *GameReader) getQuestStatus(questBytes []byte) []quest.Status { - combinedValue := uint16(questBytes[0])<<8 | uint16(questBytes[1]) + if flags == 0 { + statuses = append(statuses, quest.StatusQuestNotStarted) + return statuses + } - binaryRepresentation := fmt.Sprintf("%016b", combinedValue) + // Return early if completed + if (flags & (1 << 0)) != 0 { + statuses = append(statuses, quest.StatusUpdateQuestLogCompleted) + return statuses + } - var activeStates []quest.Status - for i, char := range binaryRepresentation { -if char == '1' { -if 15-i >= 0 && i < 16 { - activeStates = append(activeStates, quest.Status(15-i)) - } - } - } + // If not completed, find the highest bit, ignoring unknown + for bit := 10; bit >= 1; bit-- { + if (flags & (1 << bit)) != 0 { + statuses = append(statuses, quest.Status(bit)) + return statuses + } + } - return activeStates -}*/ + statuses = append(statuses, quest.StatusUnknown2) + return statuses +} From 5504aaf979e051a50f4c800998379076a91d9b64 Mon Sep 17 00:00:00 2001 From: CarlPoppa Date: Mon, 20 Oct 2025 14:14:23 +0100 Subject: [PATCH 4/4] Fix quest status reading to use correct statuses --- pkg/data/quest/quest.go | 21 +++++++++------------ pkg/data/quest/status.go | 25 +++++++++++++++++-------- pkg/memory/quests.go | 26 ++------------------------ 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/pkg/data/quest/quest.go b/pkg/data/quest/quest.go index 6ae8b0b..eec978c 100644 --- a/pkg/data/quest/quest.go +++ b/pkg/data/quest/quest.go @@ -1,21 +1,18 @@ package quest type Quest int16 -type Status int16 -type States []Status +type Status uint16 -func (s States) Completed() bool { - return s.HasStatus(StatusUpdateQuestLogCompleted) || s.HasStatus(StatusPrimaryGoalCompleted) || s.HasStatus(StatusUnknown1) || s.HasStatus(StatusUnknown2) +func (s Status) Completed() bool { + return s&StatusMaskCompleted != 0 } -func (s States) HasStatus(st Status) bool { - for _, state := range s { - if state == st { - return true - } - } +func (s Status) NotStarted() bool { + return s == 0 +} - return false +func (s Status) HasStatus(st Status) bool { + return (s & st) == st } // Note that quest order isnt what is represented visually in game @@ -49,4 +46,4 @@ const ( Act5EveOfDestruction ) -type Quests map[Quest]States +type Quests map[Quest]Status diff --git a/pkg/data/quest/status.go b/pkg/data/quest/status.go index 83ce3b6..3fef3cb 100644 --- a/pkg/data/quest/status.go +++ b/pkg/data/quest/status.go @@ -1,18 +1,27 @@ package quest const ( - StatusQuestNotStarted Status = iota + StatusRewardGranted Status = 1 << iota + StatusRewardPending + StatusStarted + StatusLeaveTown + StatusEnterArea StatusInProgress1 StatusInProgress2 StatusInProgress3 StatusInProgress4 StatusInProgress5 StatusInProgress6 - StatusInProgress8 - StatusInProgress9 - StatusInProgress10 - StatusUpdateQuestLogCompleted - StatusPrimaryGoalCompleted - StatusUnknown1 - StatusUnknown2 + StatusInProgress7 + StatusUpdateQuestLog + StatusPrimaryGoalDone + StatusCompletedNow + StatusCompletedBefore ) + +const StatusMaskCompleted = StatusPrimaryGoalDone | + StatusRewardGranted | + StatusRewardPending | + StatusCompletedNow | + StatusCompletedBefore | + StatusUpdateQuestLog diff --git a/pkg/memory/quests.go b/pkg/memory/quests.go index 03ea37b..21fa4ac 100644 --- a/pkg/memory/quests.go +++ b/pkg/memory/quests.go @@ -49,28 +49,6 @@ func (gd *GameReader) readQuestFlags(questBytes []byte, questIndex int) uint16 { return uint16(questBytes[byteOffset]) | (uint16(questBytes[byteOffset+1]) << 8) } -func (gd *GameReader) getQuestStatus(flags uint16) []quest.Status { - var statuses []quest.Status - - if flags == 0 { - statuses = append(statuses, quest.StatusQuestNotStarted) - return statuses - } - - // Return early if completed - if (flags & (1 << 0)) != 0 { - statuses = append(statuses, quest.StatusUpdateQuestLogCompleted) - return statuses - } - - // If not completed, find the highest bit, ignoring unknown - for bit := 10; bit >= 1; bit-- { - if (flags & (1 << bit)) != 0 { - statuses = append(statuses, quest.Status(bit)) - return statuses - } - } - - statuses = append(statuses, quest.StatusUnknown2) - return statuses +func (gd *GameReader) getQuestStatus(flags uint16) quest.Status { + return quest.Status(flags) }