diff --git a/pkg/data/data.go b/pkg/data/data.go index 203fc7c..dc6f832 100644 --- a/pkg/data/data.go +++ b/pkg/data/data.go @@ -66,6 +66,7 @@ type OnlineGame struct { LastGameName string LastGamePassword string FPS int + Ping int } type Panel struct { diff --git a/pkg/memory/addresses.go b/pkg/memory/addresses.go index 1b95015..eaca023 100644 --- a/pkg/memory/addresses.go +++ b/pkg/memory/addresses.go @@ -2,5 +2,4 @@ package memory const ( tzOffline = 0x29B3208 - tzOnline = 0x29B3188 ) diff --git a/pkg/memory/game_reader.go b/pkg/memory/game_reader.go index 19a6271..d178df5 100644 --- a/pkg/memory/game_reader.go +++ b/pkg/memory/game_reader.go @@ -109,6 +109,7 @@ func (gd *GameReader) GetData() data.Data { LastGameName: gd.LastGameName(), LastGamePassword: gd.LastGamePass(), FPS: gd.FPS(), + Ping: gd.Ping(), }, Monsters: monsters, Corpses: gd.Corpses(pu.Position, hover), @@ -312,7 +313,7 @@ func (gd *GameReader) GetSelectedCharacterName() string { } func (gd *GameReader) LegacyGraphics() bool { - return gd.ReadUInt(gd.Process.moduleBaseAddressPtr+0x22D845F, Uint8) != 0 + return gd.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.LegacyGraphics, Uint8) != 0 } func (gd *GameReader) IsOnline() bool { @@ -428,17 +429,23 @@ func (gd *GameReader) IsDismissableModalPresent() (bool, string) { } func (gd *GameReader) LastGameName() string { - return gd.ReadStringFromMemory(gd.moduleBaseAddressPtr+0x2587FB8, 0) + return gd.ReadStringFromMemory(gd.moduleBaseAddressPtr+0x256E668, 0) } func (gd *GameReader) LastGamePass() string { - return gd.ReadStringFromMemory(gd.moduleBaseAddressPtr+0x2588018, 0) + return gd.ReadStringFromMemory(gd.moduleBaseAddressPtr+0x256E6C8, 0) } func (gd *GameReader) FPS() int { return int(gd.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.FPS, Uint32)) } +func (gd *GameReader) Ping() int { + ptrToStructPtr := gd.moduleBaseAddressPtr + gd.offset.Ping + structPtrAddr := gd.ReadUInt(ptrToStructPtr, Uint64) + return int(gd.ReadUInt(uintptr(structPtrAddr+36), Uint32)) +} + func (gd *GameReader) HasMerc() bool { return gd.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.UI+0x8, Uint8) != 0 } diff --git a/pkg/memory/keybindings.go b/pkg/memory/keybindings.go index 3ae5124..9359a35 100644 --- a/pkg/memory/keybindings.go +++ b/pkg/memory/keybindings.go @@ -8,7 +8,7 @@ import ( ) func (gd *GameReader) GetKeyBindings() data.KeyBindings { - blob := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x1DE5C24, 0x500) + blob := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+gd.offset.KeyBindingsOffset, 0x500) blobSkills := gd.ReadBytesFromMemory(gd.offset.KeyBindingsSkillsOffset, 0x500) skillsKB := [16]data.SkillBinding{} diff --git a/pkg/memory/offset.go b/pkg/memory/offset.go index 89924a1..12b40ec 100644 --- a/pkg/memory/offset.go +++ b/pkg/memory/offset.go @@ -18,6 +18,10 @@ type Offset struct { KeyBindingsOffset uintptr KeyBindingsSkillsOffset uintptr QuestInfo uintptr + TZ uintptr + Quests uintptr + Ping uintptr + LegacyGraphics uintptr } func calculateOffsets(process *Process) Offset { @@ -75,14 +79,16 @@ func calculateOffsets(process *Process) Offset { fpsOffset := pattern - process.moduleBaseAddressPtr + 6 + fpsOffsetPtr // Keybindings - pattern = process.FindPattern(memory, "\x00\xA8\x00\x00\x00\x00\x7F\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x0F\x00\x00\x00\x00\x00\x00\x80\x4C\x6F\x00\x00\x79\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x52", "xx????x?xx????xxxxxxxxxxxxx??x?xxxxxxxxxxx") - keyBindingsOffset := pattern + 0x65 + pattern = process.FindPattern(memory, "\x48\x8D\x05\xAF\xEE", "xxxxx") + bytes = process.ReadBytesFromMemory(pattern+3, 4) + relativeOffset := int32(binary.LittleEndian.Uint32(bytes)) + keyBindingsOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(relativeOffset) // KeyBindings Skills pattern = process.FindPattern(memory, "\x0F\x10\x04\x24\x48\x6B\xC8\x1C\x48\x8D\x05", "xxxxxxxxxxx") var keyBindingsSkillsOffset uintptr bytes = process.ReadBytesFromMemory(pattern+11, 4) - relativeOffset := int32(binary.LittleEndian.Uint32(bytes)) + relativeOffset = int32(binary.LittleEndian.Uint32(bytes)) keyBindingsSkillsOffset = uintptr(int64(pattern) + 15 + int64(relativeOffset)) // QuestInfo @@ -90,6 +96,28 @@ func calculateOffsets(process *Process) Offset { questInfoOffsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32)) questInfoOffset := pattern - process.moduleBaseAddressPtr + 7 + questInfoOffsetPtr + // Terror Zones + pattern = process.FindPattern(memory, "\x48\x89\x05\xCC\xCC\xCC\xCC\x48\x8D\x05\xCC\xCC\xCC\xCC\x48\x89\x05\xCC\xCC\xCC\xCC\x48\x8D\x05\xCC\xCC\xCC\xCC\x48\x89\x15\xCC\xCC\xCC\xCC\x48\x89\x15", "xxx????xxx????xxx????xxx????xxx????xxx") + tzPtr := process.ReadUInt(pattern+3, Uint32) + tzOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(tzPtr) + + // Quest Bytes Data + pattern = process.FindPattern(memory, "\x42\xc6\x84\x28\x00\x00\x00\x00\x00\x49\xff\xc5\x49\x83\xfd\x29", "xxxx?????xxxxxxx") + bytes = process.ReadBytesFromMemory(pattern+4, 4) + questOffset := uintptr(binary.LittleEndian.Uint32(bytes)) + questDataOffset := questOffset + 1 + + // Ping + pattern = process.FindPattern(memory, "\x48\x8B\x0D\xCC\xCC\xCC\xCC\x49\x2B\xC7", "xxx????xxx") + bytes = process.ReadBytesFromMemory(pattern+3, 4) + relativeOffset = int32(binary.LittleEndian.Uint32(bytes)) + pingOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(relativeOffset) + + // LegacyGraphics + pattern = process.FindPattern(memory, "\x80\x3D\x00\x00\x00\x00\x00\x48\x8D\x54\x24\x30", "xx?????xxxxx") + legacyGfxPtr := uintptr(process.ReadUInt(pattern+2, Uint32)) + legacyGfxOffset := pattern - process.moduleBaseAddressPtr + 7 + legacyGfxPtr + return Offset{ GameData: gameDataOffset, UnitTable: unitTableOffset, @@ -104,5 +132,9 @@ func calculateOffsets(process *Process) Offset { KeyBindingsOffset: keyBindingsOffset, KeyBindingsSkillsOffset: keyBindingsSkillsOffset, QuestInfo: questInfoOffset, + TZ: tzOffset, + Quests: questDataOffset, + Ping: pingOffset, + LegacyGraphics: legacyGfxOffset, } } diff --git a/pkg/memory/player.go b/pkg/memory/player.go index d2267fe..16d1b9f 100644 --- a/pkg/memory/player.go +++ b/pkg/memory/player.go @@ -133,10 +133,20 @@ func (gd *GameReader) getSkills(skillListPtr uintptr) map[skill.ID]skill.Points quantity := gd.Process.ReadUInt(skillPtr+0x40, Uint16) charges := gd.Process.ReadUInt(skillPtr+0x48, Uint16) - skills[skill.ID(skillTxt)] = skill.Points{ - Level: lvl, - Quantity: quantity, - Charges: charges, + shouldSetSkill := true + existingSkill, exists := skills[skill.ID(skillTxt)] + if exists { + if existingSkill.Charges == 0 && charges > 0 { + shouldSetSkill = false + } + } + + if shouldSetSkill { + skills[skill.ID(skillTxt)] = skill.Points{ + Level: lvl, + Quantity: quantity, + Charges: charges, + } } skillPtr = uintptr(gd.Process.ReadUInt(skillPtr+0x08, Uint64)) diff --git a/pkg/memory/terrorzones.go b/pkg/memory/terrorzones.go index e42e980..3c07607 100644 --- a/pkg/memory/terrorzones.go +++ b/pkg/memory/terrorzones.go @@ -5,10 +5,12 @@ import ( ) func (gd *GameReader) TerrorZones() (areas []area.ID) { - tz := gd.moduleBaseAddressPtr + tzOnline + structPtr := gd.moduleBaseAddressPtr + gd.offset.TZ + zonesPtr := uintptr(gd.ReadUInt(structPtr, Uint64)) + actualActiveZoneCount := int(gd.ReadUInt(structPtr+0x8, Uint8)) - for i := 0; i < 8; i++ { - tzArea := gd.ReadUInt(tz+uintptr(i*Uint32), Uint32) + for i := 0; i < actualActiveZoneCount; i++ { + tzArea := gd.ReadUInt(zonesPtr+uintptr(i*Uint32), Uint32) if tzArea != 0 { areas = append(areas, area.ID(tzArea)) } diff --git a/pkg/nip/rule.go b/pkg/nip/rule.go index 7831de0..8559c9d 100644 --- a/pkg/nip/rule.go +++ b/pkg/nip/rule.go @@ -23,6 +23,8 @@ var ( fixedPropsRegexp = regexp.MustCompile(`(\[(type|quality|class|name|flag|color|prefix|suffix)]\s*(<=|<|>|>=|!=|==)\s*([a-zA-Z0-9]+))`) statsRegexp = regexp.MustCompile(`\[(.*?)]`) maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) + tierRegexp = regexp.MustCompile(`(\[tier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) + mercTierRegexp = regexp.MustCompile(`(\[merctier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) ) type Rule struct { @@ -31,6 +33,8 @@ type Rule struct { LineNumber int Enabled bool maxQuantity int + tier float64 + mercTier float64 stage1 *vm.Program stage2 *vm.Program requiredStats []string @@ -61,6 +65,57 @@ func (r Rules) EvaluateAll(it data.Item) (Rule, RuleResult) { return bestMatchingRule, bestMatch } +func (r Rules) EvaluateAllIgnoreTiers(it data.Item) (Rule, RuleResult) { + bestMatch := RuleResultNoMatch + bestMatchingRule := Rule{} + for _, rule := range r { + if rule.Enabled { + if rule.tier > 0 || rule.mercTier > 0 { + continue + } + result, err := rule.Evaluate(it) + if err != nil { + continue + } + if result == RuleResultFullMatch { + return rule, result + } + if result == RuleResultPartial { + bestMatch = result + bestMatchingRule = rule + } + } + } + + return bestMatchingRule, bestMatch +} + +func (r Rules) EvaluateTiers(it data.Item, tierRulesIndexes []int) (Rule, Rule) { + highestTierRule := Rule{} + highestMercTierRule := Rule{} + for _, ruleIndex := range tierRulesIndexes { + if ruleIndex < len(r) { + rule := r[ruleIndex] + if rule.Enabled { + result, err := rule.Evaluate(it) + if err != nil { + continue + } + if result == RuleResultFullMatch || result == RuleResultPartial { + if rule.tier > highestTierRule.tier { + highestTierRule = rule + } + if rule.mercTier > highestMercTierRule.mercTier { + highestMercTierRule = rule + } + } + } + } + } + + return highestTierRule, highestMercTierRule +} + var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0, "prefix": 0, "suffix": 0} func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) { @@ -77,6 +132,28 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) { rule = strings.ReplaceAll(rule, prop[0], "") } + // Try to get the tier value and purge it from the rule, we can not evaluate it yet + tier := 0.0 + for _, prop := range tierRegexp.FindAllStringSubmatch(rule, -1) { + parsedTier, err := strconv.Atoi(prop[3]) + if err != nil { + return Rule{}, fmt.Errorf("error parsing tier value %s: %w", prop[3], err) + } + tier = float64(parsedTier) + rule = strings.ReplaceAll(rule, prop[0], "") + } + + // Try to get the merctier value and purge it from the rule, we can not evaluate it yet + mercTier := 0.0 + for _, prop := range mercTierRegexp.FindAllStringSubmatch(rule, -1) { + parsedMercTier, err := strconv.Atoi(prop[3]) + if err != nil { + return Rule{}, fmt.Errorf("error parsing merctier value %s: %w", prop[3], err) + } + mercTier = float64(parsedMercTier) + rule = strings.ReplaceAll(rule, prop[0], "") + } + // Sanitize again, just in case we messed up the rule while parsing maxquantity rule = sanitizeLine(rule) if rule == "" { @@ -89,6 +166,8 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) { LineNumber: lineNumber, Enabled: true, maxQuantity: maxQuantity, + tier: tier, + mercTier: mercTier, } parts := strings.Split(rule, "#") @@ -372,3 +451,11 @@ func getRequiredStatsForRule(line string) []string { func (r Rule) MaxQuantity() int { return r.maxQuantity } + +func (r Rule) Tier() float64 { + return r.tier +} + +func (r Rule) MercTier() float64 { + return r.mercTier +}