Panel manager addition (#36)
* Added panelManager support for d2go (crude implementation but works) * Refined a little bit * Made widget map a more mappy map which can be access without iterations * Added widgets to debugger
This commit is contained in:
@@ -38,6 +38,7 @@ type Data struct {
|
|||||||
AdjacentLevels []Level
|
AdjacentLevels []Level
|
||||||
Rooms []Room
|
Rooms []Room
|
||||||
OpenMenus OpenMenus
|
OpenMenus OpenMenus
|
||||||
|
Widgets map[string]map[string]interface{}
|
||||||
Roster Roster
|
Roster Roster
|
||||||
HoverData HoverData
|
HoverData HoverData
|
||||||
TerrorZones []area.ID
|
TerrorZones []area.ID
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
Inventory: gd.Inventory(rawPlayerUnits, hover),
|
Inventory: gd.Inventory(rawPlayerUnits, hover),
|
||||||
Objects: gd.Objects(pu.Position, hover),
|
Objects: gd.Objects(pu.Position, hover),
|
||||||
OpenMenus: gd.openMenus(),
|
OpenMenus: gd.openMenus(),
|
||||||
|
Widgets: gd.UpdateWidgets(),
|
||||||
Roster: roster,
|
Roster: roster,
|
||||||
HoverData: hover,
|
HoverData: hover,
|
||||||
TerrorZones: gd.TerrorZones(),
|
TerrorZones: gd.TerrorZones(),
|
||||||
@@ -191,9 +192,10 @@ func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
|
|||||||
|
|
||||||
// TODO: Take a look to better ways to get this data, now it's very flakky, is just a random memory position + not in game
|
// TODO: Take a look to better ways to get this data, now it's very flakky, is just a random memory position + not in game
|
||||||
func (gd *GameReader) InCharacterSelectionScreen() bool {
|
func (gd *GameReader) InCharacterSelectionScreen() bool {
|
||||||
uiBase := gd.Process.moduleBaseAddressPtr + gd.offset.UI - 0xA
|
cs_visible, err := gd.IsWidgetVisible("CharacterSelectPanel")
|
||||||
|
if err != nil {
|
||||||
return gd.Process.ReadUInt(uiBase, Uint8) != 1 && gd.Process.ReadUInt(gd.moduleBaseAddressPtr+2136526, Uint8) == 0
|
}
|
||||||
|
return cs_visible
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) GetSelectedCharacterName() string {
|
func (gd *GameReader) GetSelectedCharacterName() string {
|
||||||
@@ -224,6 +226,7 @@ func (gd *GameReader) IsInCharacterSelectionScreen() bool {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func (gd *GameReader) IsInCharacterCreationScreen() bool {
|
func (gd *GameReader) IsInCharacterCreationScreen() bool {
|
||||||
|
// Dont use this ;)
|
||||||
// This will bug out if you switch to legacy graphics in the character select screen and return 1 until you go back to character screen with d2r graphics
|
// This will bug out if you switch to legacy graphics in the character select screen and return 1 until you go back to character screen with d2r graphics
|
||||||
return gd.ReadUInt(gd.moduleBaseAddressPtr+0x234A1CE, 1) == 1
|
return gd.ReadUInt(gd.moduleBaseAddressPtr+0x234A1CE, 1) == 1
|
||||||
}
|
}
|
||||||
@@ -239,3 +242,42 @@ func (gd *GameReader) LastGamePass() string {
|
|||||||
func (gd *GameReader) FPS() int {
|
func (gd *GameReader) FPS() int {
|
||||||
return int(gd.ReadUInt(gd.moduleBaseAddressPtr+0x2140DF4, 4))
|
return int(gd.ReadUInt(gd.moduleBaseAddressPtr+0x2140DF4, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) UpdateWidgets() map[string]map[string]interface{} {
|
||||||
|
widgets := map[string]map[string]interface{}{}
|
||||||
|
|
||||||
|
if gd.offset.PanelManagerContainerOffset == 0 {
|
||||||
|
gd.offset = calculateOffsets(gd.Process)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming PanelManagerContainer address is known and stored in gd
|
||||||
|
panelManagerContainerPtrAddr := gd.Process.moduleBaseAddressPtr + gd.offset.PanelManagerContainerOffset
|
||||||
|
|
||||||
|
// Read the PanelManagerContainer pointer value
|
||||||
|
panelManagerContainerAddr, err := gd.Process.ReadPointer(panelManagerContainerPtrAddr, 8)
|
||||||
|
if err != nil {
|
||||||
|
return widgets
|
||||||
|
}
|
||||||
|
// Read the Panel Managers WidgetContainer
|
||||||
|
widgetContainer, err := gd.Process.ReadWidgetContainer(panelManagerContainerAddr, true)
|
||||||
|
if err != nil {
|
||||||
|
return widgets
|
||||||
|
}
|
||||||
|
// Read the list of child widgets
|
||||||
|
childWidgets, err := gd.Process.ReadWidgetList(widgetContainer["ChildWidgetsListPointer"].(uintptr), int(widgetContainer["ChildWidgetSize"].(uint)))
|
||||||
|
if err != nil {
|
||||||
|
return widgets
|
||||||
|
}
|
||||||
|
return childWidgets
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWidgetVisible checks if any child widget on the PanelManager has the same name and has the Active and Visible booleans on.
|
||||||
|
func (gd *GameReader) IsWidgetVisible(widgetName string) (bool, error) {
|
||||||
|
widgets := gd.UpdateWidgets()
|
||||||
|
widget, exists := widgets[widgetName]
|
||||||
|
if !exists {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return widget["WidgetActive"].(bool) && widget["WidgetVisible"].(bool), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package memory
|
package memory
|
||||||
|
|
||||||
import "encoding/binary"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
type Offset struct {
|
type Offset struct {
|
||||||
GameData uintptr
|
GameData uintptr
|
||||||
@@ -9,6 +11,7 @@ type Offset struct {
|
|||||||
Hover uintptr
|
Hover uintptr
|
||||||
Expansion uintptr
|
Expansion uintptr
|
||||||
RosterOffset uintptr
|
RosterOffset uintptr
|
||||||
|
PanelManagerContainerOffset uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateOffsets(process Process) Offset {
|
func calculateOffsets(process Process) Offset {
|
||||||
@@ -45,6 +48,11 @@ func calculateOffsets(process Process) Offset {
|
|||||||
offsetPtr = uintptr(process.ReadUInt(pattern-3, Uint32))
|
offsetPtr = uintptr(process.ReadUInt(pattern-3, Uint32))
|
||||||
rosterOffset := pattern - process.moduleBaseAddressPtr + 1 + offsetPtr
|
rosterOffset := pattern - process.moduleBaseAddressPtr + 1 + offsetPtr
|
||||||
|
|
||||||
|
// PanelManagerContainer
|
||||||
|
pattern = process.FindPatternByOperand(memory, "\x48\x89\x05\x00\x00\x00\x00\x48\x85\xDB\x74\x1E", "xxx????xxxxx")
|
||||||
|
bytes = process.ReadBytesFromMemory(pattern, 8)
|
||||||
|
panelManagerContainerOffset := (pattern - process.moduleBaseAddressPtr) // uintptr(binary.LittleEndian.Uint64(bytes))
|
||||||
|
|
||||||
return Offset{
|
return Offset{
|
||||||
GameData: gameDataOffset,
|
GameData: gameDataOffset,
|
||||||
UnitTable: unitTableOffset,
|
UnitTable: unitTableOffset,
|
||||||
@@ -52,5 +60,6 @@ func calculateOffsets(process Process) Offset {
|
|||||||
Hover: uintptr(hoverOffset),
|
Hover: uintptr(hoverOffset),
|
||||||
Expansion: expOffset,
|
Expansion: expOffset,
|
||||||
RosterOffset: rosterOffset,
|
RosterOffset: rosterOffset,
|
||||||
|
PanelManagerContainerOffset: panelManagerContainerOffset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,18 @@ func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Process) FindPatternByOperand(memory []byte, pattern, mask string) uintptr {
|
||||||
|
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||||
|
// Adjust the address based on the operand value
|
||||||
|
operandAddress := p.moduleBaseAddressPtr + uintptr(offset)
|
||||||
|
operandValue := binary.LittleEndian.Uint32(memory[offset+3 : offset+7])
|
||||||
|
finalAddress := operandAddress + uintptr(operandValue) + 7 // 7 is the length of the instruction
|
||||||
|
return finalAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (p Process) GetPID() uint32 {
|
func (p Process) GetPID() uint32 {
|
||||||
return p.pid
|
return p.pid
|
||||||
}
|
}
|
||||||
@@ -234,3 +246,97 @@ func GetProcessModules(processID uint32) ([]ModuleInfo, error) {
|
|||||||
|
|
||||||
return moduleInfos, nil
|
return moduleInfos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadPointer reads a pointer from the specified memory address.
|
||||||
|
func (p *Process) ReadPointer(address uintptr, size int) (uintptr, error) {
|
||||||
|
buffer := p.ReadBytesFromMemory(address, uint(size))
|
||||||
|
if len(buffer) == 0 {
|
||||||
|
return 0, errors.New("failed to read memory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uintptr(*(*uint64)(unsafe.Pointer(&buffer[0]))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadWidgetContainer reads the WidgetContainer structure.
|
||||||
|
func (p *Process) ReadWidgetContainer(address uintptr, full bool) (map[string]interface{}, error) {
|
||||||
|
widgetPtr, err := p.ReadPointer(address+0x8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetNameLength := p.ReadUInt(address+0x10, 4)
|
||||||
|
|
||||||
|
widgetName := p.ReadStringFromMemory(widgetPtr, uint(widgetNameLength))
|
||||||
|
if widgetName == "" {
|
||||||
|
return nil, errors.New("failed to read widget name")
|
||||||
|
}
|
||||||
|
|
||||||
|
widget_visible := p.ReadUInt(address+0x51, 1) == 1
|
||||||
|
widget_active := p.ReadUInt(address+0x50, 1) == 1
|
||||||
|
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"WidgetNameString": widgetName,
|
||||||
|
"WidgetNameLength": widgetNameLength,
|
||||||
|
"WidgetVisible": widget_visible,
|
||||||
|
"WidgetActive": widget_active,
|
||||||
|
}
|
||||||
|
|
||||||
|
if full {
|
||||||
|
childWidgetsListPtr, err := p.ReadPointer(widgetPtr+0x38, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
childWidgetSize := p.ReadUInt(widgetPtr+0x40, 4)
|
||||||
|
|
||||||
|
widgetListPtr, err := p.ReadPointer(widgetPtr+0x68, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetListSize := p.ReadUInt(widgetPtr+0x78, 4)
|
||||||
|
|
||||||
|
widgetList2Ptr, err := p.ReadPointer(widgetPtr+0x80, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetList2Size := p.ReadUInt(widgetPtr+0x90, 4)
|
||||||
|
|
||||||
|
result["ChildWidgetsListPointer"] = childWidgetsListPtr
|
||||||
|
result["ChildWidgetSize"] = childWidgetSize
|
||||||
|
result["WidgetListPointer"] = widgetListPtr
|
||||||
|
result["WidgetListSize"] = widgetListSize
|
||||||
|
result["WidgetList2Pointer"] = widgetList2Ptr
|
||||||
|
result["WidgetList2Size"] = widgetList2Size
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadWidgetList iterates through a list of widgets given a pointer to the list and its size.
|
||||||
|
func (p *Process) ReadWidgetList(listPointer uintptr, listSize int) (map[string]map[string]interface{}, error) {
|
||||||
|
widgetMap := make(map[string]map[string]interface{})
|
||||||
|
widgetSize := int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
|
||||||
|
for i := 0; i < listSize; i++ {
|
||||||
|
widgetAddr, err := p.ReadPointer(listPointer+uintptr(i*widgetSize), 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetContainer, err := p.ReadWidgetContainer(widgetAddr, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetName, ok := widgetContainer["WidgetNameString"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("failed to read widget name")
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetMap[widgetName] = widgetContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
return widgetMap, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user