diff --git a/pkg/memory/panels.go b/pkg/memory/panels.go index e6e5cf7..9ceda88 100644 --- a/pkg/memory/panels.go +++ b/pkg/memory/panels.go @@ -1,15 +1,9 @@ package memory import ( - "bytes" - "regexp" - "strings" - "github.com/hectorgimenez/d2go/pkg/data" ) -var tooltipRenderPriceSnippet = regexp.MustCompile(`[\x20-\x7e\n]{0,512}(?:Sell value|Cost|Price):?\s*\d[\d,.]*[\x20-\x7e\n]{0,160}`) - func NewPanel(panelPtr uintptr, panelParent string, depth int, gd *GameReader) *data.Panel { //itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x158, Uint64)) panel := &data.Panel{ PanelPtr: panelPtr, @@ -48,204 +42,6 @@ func GetText(p data.Panel) string { return "" } -// VisiblePanelTexts returns readable text from every visible panel in traversal -// order. Tooltip text is assembled by the client after hover, so callers can -// use this as a live UI source instead of recalculating item values. -func (gd *GameReader) VisiblePanelTexts() []string { - if gd == nil || gd.Process == nil { - return nil - } - base := gd.Process.moduleBaseAddressPtr + gd.offset.PanelManagerContainerOffset - panelStructPtr := uintptr(gd.Process.ReadUInt(base, Uint64)) - panelPtr := uintptr(gd.Process.ReadUInt(panelStructPtr+0x58, Uint64)) - numChildren := int(gd.Process.ReadUInt(panelStructPtr+0x60, Uint8)) - - out := make([]string, 0, 64) - seen := make(map[string]struct{}, 64) - collectVisiblePanelTexts(panelPtr, numChildren, &out, seen, gd) - return out -} - -// TooltipRenderTextsFast scans render/cache pages anchored by visible panel -// child arrays. The client keeps item tooltip strings here even when the -// regular panel tree has no readable children. -func (gd *GameReader) TooltipRenderTextsFast() []string { - return gd.tooltipRenderTexts() -} - -func (gd *GameReader) tooltipRenderTexts() []string { - if gd == nil || gd.Process == nil { - return nil - } - base := gd.Process.moduleBaseAddressPtr + gd.offset.PanelManagerContainerOffset - panelStructPtr := uintptr(gd.Process.ReadUInt(base, Uint64)) - panelPtr := uintptr(gd.Process.ReadUInt(panelStructPtr+0x58, Uint64)) - numChildren := int(gd.Process.ReadUInt(panelStructPtr+0x60, Uint8)) - - childPages := make(map[uintptr]struct{}, 64) - collectPanelChildPages(panelPtr, numChildren, childPages, gd) - - scanPages := make(map[uintptr]struct{}, 256) - for page := range childPages { - for _, delta := range []int{-0x2000, -0x1000, 0, 0x1000, 0x2000} { - if addr := pageFromDelta(page, delta); addr != 0 { - scanPages[addr] = struct{}{} - } - } - } - - out := make([]string, 0, 16) - seen := make(map[string]struct{}, 16) - for page := range scanPages { - buf := gd.Process.ReadBytesFromMemory(page, 0x1000) - if len(buf) == 0 { - continue - } - for _, match := range tooltipRenderPriceSnippet.FindAll(buf, -1) { - text := cleanTooltipRenderSnippet(match) - if text == "" { - continue - } - if _, ok := seen[text]; ok { - continue - } - seen[text] = struct{}{} - out = append(out, text) - } - for _, pageText := range utf16LEPrintablePages(buf) { - for _, match := range tooltipRenderPriceSnippet.FindAllString(pageText, -1) { - text := cleanTooltipRenderText(match) - if text == "" { - continue - } - if _, ok := seen[text]; ok { - continue - } - seen[text] = struct{}{} - out = append(out, text) - } - } - } - return out -} - -func collectPanelChildPages(panelPtr uintptr, numChildren int, pages map[uintptr]struct{}, gd *GameReader) { - if numChildren <= 0 || numChildren >= 80 { - return - } - for i := 0; i < numChildren; i++ { - panelStructPtr := uintptr(gd.Process.ReadUInt(uintptr(uint64(panelPtr)+uint64(i*8)), Uint64)) - if panelStructPtr == 0 { - continue - } - childPtr := uintptr(gd.Process.ReadUInt(panelStructPtr+0x58, Uint64)) - childCount := int(gd.Process.ReadUInt(panelStructPtr+0x60, Uint8)) - if childPtr != 0 { - pages[childPtr&^uintptr(0xfff)] = struct{}{} - } - if childCount > 0 && childCount < 50 { - collectPanelChildPages(childPtr, childCount, pages, gd) - } - } -} - -func pageFromDelta(page uintptr, delta int) uintptr { - addr := int64(page) + int64(delta) - if addr <= 0 { - return 0 - } - return uintptr(addr) & ^uintptr(0xfff) -} - -func cleanTooltipRenderSnippet(raw []byte) string { - raw = bytes.Trim(raw, "\x00") - return cleanTooltipRenderText(string(raw)) -} - -func cleanTooltipRenderText(raw string) string { - s := strings.TrimSpace(raw) - if s == "" { - return "" - } - lines := strings.Split(s, "\n") - cleaned := make([]string, 0, len(lines)) - for _, line := range lines { - line = strings.TrimSpace(line) - line = strings.TrimLeft(line, "0123456789") - line = strings.TrimSpace(line) - if line != "" { - cleaned = append(cleaned, line) - } - } - return strings.Join(cleaned, "\n") -} - -func utf16LEPrintablePages(buf []byte) []string { - out := make([]string, 0, 2) - for align := 0; align < 2; align++ { - var b strings.Builder - lastWasNL := true - for i := align; i+1 < len(buf); i += 2 { - lo := buf[i] - hi := buf[i+1] - if hi == 0 && (lo == '\n' || lo == '\r' || lo == '\t' || (lo >= 0x20 && lo <= 0x7e)) { - if lo == '\r' || lo == '\t' { - lo = '\n' - } - if lo == '\n' { - if !lastWasNL { - b.WriteByte('\n') - lastWasNL = true - } - continue - } - b.WriteByte(lo) - lastWasNL = false - continue - } - if !lastWasNL { - b.WriteByte('\n') - lastWasNL = true - } - } - text := strings.TrimSpace(b.String()) - if len(text) >= 16 { - out = append(out, text) - } - } - return out -} - -func collectVisiblePanelTexts(panelPtr uintptr, numChildren int, out *[]string, seen map[string]struct{}, gd *GameReader) { - if numChildren <= 0 || numChildren >= 50 { - return - } - for i := 0; i < numChildren; i++ { - panelStructPtr := uintptr(gd.Process.ReadUInt(uintptr(uint64(panelPtr)+uint64(i*8)), Uint64)) - if panelStructPtr == 0 { - continue - } - p := data.Panel{ - PanelEnabled: gd.Process.ReadUInt(panelStructPtr+0x50, Uint8) != 0, - PanelVisible: gd.Process.ReadUInt(panelStructPtr+0x51, Uint8) != 0, - PtrChild: uintptr(gd.Process.ReadUInt(panelStructPtr+0x58, Uint64)), - NumChildren: int(gd.Process.ReadUInt(panelStructPtr+0x60, Uint8)), - ExtraText: gd.Process.ReadStringFromMemory(panelStructPtr+0xA0, 0), - ExtraText2: gd.Process.ReadStringFromMemory(uintptr(gd.Process.ReadUInt(panelStructPtr+0x290, Uint64)), 0), - ExtraText3: gd.Process.ReadStringFromMemory(uintptr(gd.Process.ReadUInt(panelStructPtr+0x88, Uint64)), 0), - } - if p.PanelVisible && p.PanelEnabled { - if text := strings.TrimSpace(GetText(p)); text != "" { - if _, ok := seen[text]; !ok { - seen[text] = struct{}{} - *out = append(*out, text) - } - } - collectVisiblePanelTexts(p.PtrChild, p.NumChildren, out, seen, gd) - } - } -} - func cleanString(input string) string { return input // Replace newlines and carriage returns as needed }