Add some memory reading stuff, based on many other open source projects
This commit is contained in:
6
go.mod
6
go.mod
@@ -2,7 +2,11 @@ module github.com/hectorgimenez/d2go
|
|||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.8.2
|
require (
|
||||||
|
github.com/stretchr/testify v1.8.2
|
||||||
|
github.com/winlabs/gowin32 v0.0.0-20221003142512-0d265587d3c9
|
||||||
|
golang.org/x/sys v0.6.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -10,6 +10,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/winlabs/gowin32 v0.0.0-20221003142512-0d265587d3c9 h1:U8aCPFEMnxAEyj9IonhMVV1gSL4nzelh8uvoXp0hrq0=
|
||||||
|
github.com/winlabs/gowin32 v0.0.0-20221003142512-0d265587d3c9/go.mod h1:N51TYkG9JGR5sytj0EoPl31Xg2kuB507lxEmrwSNvfQ=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package game
|
package data
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package game
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/area"
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package game
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/item"
|
"github.com/hectorgimenez/d2go/pkg/data/item"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package game
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hectorgimenez/d2go/pkg/data/npc"
|
"github.com/hectorgimenez/d2go/pkg/data/npc"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package game
|
package data
|
||||||
|
|
||||||
import "github.com/hectorgimenez/d2go/pkg/data/object"
|
import "github.com/hectorgimenez/d2go/pkg/data/object"
|
||||||
|
|
||||||
|
|||||||
118
pkg/memory/game_reader.go
Normal file
118
pkg/memory/game_reader.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GameReader struct {
|
||||||
|
offset Offset
|
||||||
|
Process
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGameReader(process Process) *GameReader {
|
||||||
|
return &GameReader{
|
||||||
|
offset: calculateOffsets(process),
|
||||||
|
Process: process,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousData *data.Data
|
||||||
|
|
||||||
|
func (gd *GameReader) GetData() data.Data {
|
||||||
|
if gd.offset.UnitTable == 0 {
|
||||||
|
gd.offset = calculateOffsets(gd.Process)
|
||||||
|
}
|
||||||
|
|
||||||
|
roster := gd.getRoster()
|
||||||
|
playerUnitPtr, corpse := gd.getPlayerUnitPtr(roster)
|
||||||
|
|
||||||
|
pu := gd.GetPlayerUnit(playerUnitPtr)
|
||||||
|
|
||||||
|
d := data.Data{
|
||||||
|
Corpse: corpse,
|
||||||
|
Monsters: gd.Monsters(pu.Position),
|
||||||
|
PlayerUnit: pu,
|
||||||
|
Items: gd.Items(pu.Position),
|
||||||
|
Objects: gd.Objects(pu.Position),
|
||||||
|
OpenMenus: gd.openMenus(),
|
||||||
|
Roster: roster,
|
||||||
|
}
|
||||||
|
|
||||||
|
if playerUnitPtr == 0 {
|
||||||
|
return *previousData
|
||||||
|
}
|
||||||
|
|
||||||
|
previousData = &d
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) InGame() bool {
|
||||||
|
pu, _ := gd.getPlayerUnitPtr([]data.RosterMember{})
|
||||||
|
|
||||||
|
return pu > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) openMenus() data.OpenMenus {
|
||||||
|
uiBase := gd.Process.moduleBaseAddressPtr + gd.offset.UI - 0xA
|
||||||
|
|
||||||
|
buffer := gd.Process.ReadBytesFromMemory(uiBase, 0x169)
|
||||||
|
|
||||||
|
isMapShown := gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.UI, Uint8)
|
||||||
|
|
||||||
|
return data.OpenMenus{
|
||||||
|
Inventory: buffer[0x01] != 0,
|
||||||
|
LoadingScreen: buffer[0x168] != 0,
|
||||||
|
NPCInteract: buffer[0x08] != 0,
|
||||||
|
NPCShop: buffer[0x0B] != 0,
|
||||||
|
Stash: buffer[0x18] != 0,
|
||||||
|
Waypoint: buffer[0x13] != 0,
|
||||||
|
MapShown: isMapShown != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) hoveredData() (hoveredUnitID uint, hoveredType uint, isHovered bool) {
|
||||||
|
hoverAddressPtr := gd.Process.moduleBaseAddressPtr + gd.offset.Hover
|
||||||
|
hoverBuffer := gd.Process.ReadBytesFromMemory(hoverAddressPtr, 12)
|
||||||
|
isUnitHovered := ReadUIntFromBuffer(hoverBuffer, 0, Uint16)
|
||||||
|
if isUnitHovered > 0 {
|
||||||
|
hoveredType = ReadUIntFromBuffer(hoverBuffer, 0x04, Uint32)
|
||||||
|
hoveredUnitID = ReadUIntFromBuffer(hoverBuffer, 0x08, Uint32)
|
||||||
|
|
||||||
|
return hoveredUnitID, hoveredType, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatData(statEnum, statValue uint) (stat.Stat, int) {
|
||||||
|
value := int(statValue)
|
||||||
|
switch stat.Stat(statEnum) {
|
||||||
|
case stat.Life,
|
||||||
|
stat.MaxLife,
|
||||||
|
stat.Mana,
|
||||||
|
stat.MaxMana,
|
||||||
|
stat.Stamina,
|
||||||
|
stat.LifePerLevel,
|
||||||
|
stat.ManaPerLevel:
|
||||||
|
value = int(statValue >> 8)
|
||||||
|
case stat.ColdLength,
|
||||||
|
stat.PoisonLength:
|
||||||
|
value = int(statValue / 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat.Stat(statEnum), value
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProperties(item *data.Item, flags uint32) {
|
||||||
|
if 0x00400000&flags != 0 {
|
||||||
|
item.Ethereal = true
|
||||||
|
}
|
||||||
|
if 0x00000010&flags != 0 {
|
||||||
|
item.Identified = true
|
||||||
|
}
|
||||||
|
if 0x00002000&flags != 0 {
|
||||||
|
item.IsVendor = true
|
||||||
|
}
|
||||||
|
}
|
||||||
131
pkg/memory/item.go
Normal file
131
pkg/memory/item.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/item"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gd *GameReader) Items(playerPosition data.Position) data.Items {
|
||||||
|
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||||
|
|
||||||
|
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
||||||
|
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||||
|
|
||||||
|
items := data.Items{}
|
||||||
|
for i := 0; i < 128; i++ {
|
||||||
|
itemOffset := 8 * i
|
||||||
|
itemUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(itemOffset), Uint64))
|
||||||
|
for itemUnitPtr > 0 {
|
||||||
|
itemDataBuffer := gd.Process.ReadBytesFromMemory(itemUnitPtr, 144)
|
||||||
|
itemType := ReadUIntFromBuffer(itemDataBuffer, 0x00, Uint32)
|
||||||
|
txtFileNo := ReadUIntFromBuffer(itemDataBuffer, 0x04, Uint32)
|
||||||
|
unitID := ReadUIntFromBuffer(itemDataBuffer, 0x08, Uint32)
|
||||||
|
// itemLoc = 0 in inventory, 1 equipped, 2 in belt, 3 on ground, 4 cursor, 5 dropping, 6 socketed
|
||||||
|
itemLoc := ReadUIntFromBuffer(itemDataBuffer, 0x0C, Uint32)
|
||||||
|
|
||||||
|
if itemType != 4 {
|
||||||
|
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
unitDataPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x10, Uint64))
|
||||||
|
unitDataBuffer := gd.Process.ReadBytesFromMemory(unitDataPtr, 144)
|
||||||
|
flags := ReadUIntFromBuffer(unitDataBuffer, 0x18, Uint32)
|
||||||
|
invPage := ReadUIntFromBuffer(unitDataBuffer, 0x55, Uint8)
|
||||||
|
itemQuality := ReadUIntFromBuffer(unitDataBuffer, 0x00, Uint32)
|
||||||
|
//itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32)
|
||||||
|
|
||||||
|
// Item coordinates (X, Y)
|
||||||
|
pathPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x38, Uint64))
|
||||||
|
pathBuffer := gd.Process.ReadBytesFromMemory(pathPtr, 144)
|
||||||
|
itemX := ReadUIntFromBuffer(pathBuffer, 0x10, Uint16)
|
||||||
|
itemY := ReadUIntFromBuffer(pathBuffer, 0x14, Uint16)
|
||||||
|
|
||||||
|
// Item Stats
|
||||||
|
statsListExPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x88, Uint64))
|
||||||
|
statsListExBuffer := gd.Process.ReadBytesFromMemory(statsListExPtr, 180)
|
||||||
|
statPtr := uintptr(ReadUIntFromBuffer(statsListExBuffer, 0x30, Uint64))
|
||||||
|
statCount := ReadUIntFromBuffer(statsListExBuffer, 0x38, Uint32)
|
||||||
|
statExPtr := uintptr(ReadUIntFromBuffer(statsListExBuffer, 0x88, Uint64))
|
||||||
|
statExCount := ReadUIntFromBuffer(statsListExBuffer, 0x90, Uint32)
|
||||||
|
|
||||||
|
stats := gd.getItemStats(statCount, statPtr, statExCount, statExPtr)
|
||||||
|
|
||||||
|
name := item.GetNameByEnum(txtFileNo)
|
||||||
|
itemHovered := false
|
||||||
|
if isHovered && hoveredType == 4 && hoveredUnitID == unitID {
|
||||||
|
itemHovered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
itm := data.Item{
|
||||||
|
UnitID: data.UnitID(unitID),
|
||||||
|
Name: name,
|
||||||
|
Quality: item.Quality(itemQuality),
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(itemX),
|
||||||
|
Y: int(itemY),
|
||||||
|
},
|
||||||
|
IsHovered: itemHovered,
|
||||||
|
Stats: stats,
|
||||||
|
}
|
||||||
|
setProperties(&itm, uint32(flags))
|
||||||
|
|
||||||
|
switch itemLoc {
|
||||||
|
case 0:
|
||||||
|
if itm.IsVendor {
|
||||||
|
items.Shop = append(items.Shop, itm)
|
||||||
|
} else if invPage == 0 {
|
||||||
|
items.Inventory = append(items.Inventory, itm)
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
items.Equipped = append(items.Equipped, itm)
|
||||||
|
case 2:
|
||||||
|
items.Belt = append(items.Belt, itm)
|
||||||
|
case 3, 5:
|
||||||
|
items.Ground = append(items.Ground, itm)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(items.Ground, func(i, j int) bool {
|
||||||
|
distanceI := utils.DistanceFromPoint(playerPosition, items.Ground[i].Position)
|
||||||
|
distanceJ := utils.DistanceFromPoint(playerPosition, items.Ground[j].Position)
|
||||||
|
|
||||||
|
return distanceI < distanceJ
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) getItemStats(statCount uint, statPtr uintptr, statExCount uint, statExPtr uintptr) map[stat.Stat]int {
|
||||||
|
stats := map[stat.Stat]int{}
|
||||||
|
|
||||||
|
if statCount < 20 && statCount > 0 {
|
||||||
|
statBuffer := gd.Process.ReadBytesFromMemory(statPtr, statCount*10)
|
||||||
|
for i := 0; i < int(statCount); i++ {
|
||||||
|
offset := uint(i * 8)
|
||||||
|
statEnum := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint16)
|
||||||
|
statValue := ReadUIntFromBuffer(statBuffer, offset+0x4, Uint32)
|
||||||
|
st, value := getStatData(statEnum, statValue)
|
||||||
|
stats[st] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if statExCount < 20 && statCount > 0 {
|
||||||
|
statBuffer := gd.Process.ReadBytesFromMemory(statExPtr, statExCount*10)
|
||||||
|
for i := 0; i < int(statExCount); i++ {
|
||||||
|
offset := uint(i * 8)
|
||||||
|
statEnum := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint16)
|
||||||
|
statValue := ReadUIntFromBuffer(statBuffer, offset+0x4, Uint32)
|
||||||
|
st, value := getStatData(statEnum, statValue)
|
||||||
|
stats[st] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
201
pkg/memory/monsters.go
Normal file
201
pkg/memory/monsters.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/npc"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gd *GameReader) Monsters(playerPosition data.Position) data.Monsters {
|
||||||
|
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||||
|
|
||||||
|
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024
|
||||||
|
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||||
|
|
||||||
|
monsters := data.Monsters{}
|
||||||
|
for i := 0; i < 128; i++ {
|
||||||
|
monsterOffset := 8 * i
|
||||||
|
monsterUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(monsterOffset), Uint64))
|
||||||
|
for monsterUnitPtr > 0 {
|
||||||
|
monsterDataBuffer := gd.Process.ReadBytesFromMemory(monsterUnitPtr, 144)
|
||||||
|
|
||||||
|
//monsterType := ReadUIntFromBuffer(monsterDataBuffer, 0x00, Uint32)
|
||||||
|
txtFileNo := ReadUIntFromBuffer(monsterDataBuffer, 0x04, Uint32)
|
||||||
|
if !gd.shouldBeIgnored(txtFileNo) {
|
||||||
|
unitID := ReadUIntFromBuffer(monsterDataBuffer, 0x08, Uint32)
|
||||||
|
|
||||||
|
//mode := ReadUIntFromBuffer(monsterDataBuffer, 0x0C, Uint32)
|
||||||
|
|
||||||
|
unitDataPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x10, Uint64))
|
||||||
|
//isUnique := gd.Process.ReadUInt(unitDataPtr+0x18, Uint16)
|
||||||
|
flag := gd.Process.ReadBytesFromMemory(unitDataPtr+0x1A, Uint8)[0]
|
||||||
|
isCorpse := gd.Process.ReadUInt(monsterUnitPtr+0x1A6, Uint8)
|
||||||
|
|
||||||
|
//unitDataBuffer := gd.Process.ReadBytesFromMemory(unitDataPtr, 144)
|
||||||
|
|
||||||
|
// Coordinates (X, Y)
|
||||||
|
pathPtr := uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x38, Uint64))
|
||||||
|
posX := gd.Process.ReadUInt(pathPtr+0x02, Uint16)
|
||||||
|
posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16)
|
||||||
|
|
||||||
|
hovered := false
|
||||||
|
if isHovered && hoveredType == 1 && hoveredUnitID == unitID {
|
||||||
|
hovered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
statsListExPtr := uintptr(ReadUIntFromBuffer(monsterDataBuffer, 0x88, Uint64))
|
||||||
|
statPtr := uintptr(gd.Process.ReadUInt(statsListExPtr+0x30, Uint64))
|
||||||
|
statCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64)
|
||||||
|
|
||||||
|
stats := gd.getMonsterStats(statCount, statPtr)
|
||||||
|
|
||||||
|
m := data.Monster{
|
||||||
|
UnitID: data.UnitID(unitID),
|
||||||
|
Name: npc.ID(int(txtFileNo)),
|
||||||
|
IsHovered: hovered,
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(posX),
|
||||||
|
Y: int(posY),
|
||||||
|
},
|
||||||
|
Stats: stats,
|
||||||
|
Type: getMonsterType(flag),
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCorpse == 0 {
|
||||||
|
monsters = append(monsters, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
monsterUnitPtr = uintptr(gd.Process.ReadUInt(monsterUnitPtr+0x150, Uint64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(monsters, func(i, j int) bool {
|
||||||
|
distanceI := utils.DistanceFromPoint(playerPosition, monsters[i].Position)
|
||||||
|
distanceJ := utils.DistanceFromPoint(playerPosition, monsters[j].Position)
|
||||||
|
|
||||||
|
return distanceI < distanceJ
|
||||||
|
})
|
||||||
|
|
||||||
|
return monsters
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMonsterType(typeFlag byte) data.MonsterType {
|
||||||
|
switch typeFlag {
|
||||||
|
case 10:
|
||||||
|
return data.MonsterTypeSuperUnique
|
||||||
|
case 1 << 2:
|
||||||
|
return data.MonsterTypeChampion
|
||||||
|
case 1 << 3:
|
||||||
|
return data.MonsterTypeUnique
|
||||||
|
case 1 << 4:
|
||||||
|
return data.MonsterTypeMinion
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.MonsterTypeNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) getMonsterStats(statCount uint, statPtr uintptr) map[stat.Stat]int {
|
||||||
|
stats := map[stat.Stat]int{}
|
||||||
|
|
||||||
|
if statCount > 0 {
|
||||||
|
statBuffer := gd.Process.ReadBytesFromMemory(statPtr+0x2, statCount*8)
|
||||||
|
for i := 0; i < int(statCount); i++ {
|
||||||
|
offset := uint(i * 8)
|
||||||
|
statEnum := ReadUIntFromBuffer(statBuffer, offset, Uint16)
|
||||||
|
statValue := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint32)
|
||||||
|
stats[stat.Stat(statEnum)] = int(statValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) shouldBeIgnored(txtNo uint) bool {
|
||||||
|
switch npc.ID(txtNo) {
|
||||||
|
case npc.Chicken,
|
||||||
|
npc.Rat,
|
||||||
|
npc.Rogue,
|
||||||
|
npc.HellMeteor,
|
||||||
|
npc.Bird,
|
||||||
|
npc.Bird2,
|
||||||
|
npc.Bat,
|
||||||
|
npc.Act2Male,
|
||||||
|
npc.Act2Female,
|
||||||
|
npc.Act2Child,
|
||||||
|
npc.Cow,
|
||||||
|
npc.Camel,
|
||||||
|
npc.Act2Guard,
|
||||||
|
npc.Act2Vendor,
|
||||||
|
npc.Act2Vendor2,
|
||||||
|
npc.Maggot,
|
||||||
|
npc.Bug,
|
||||||
|
npc.Scorpion,
|
||||||
|
npc.Rogue2,
|
||||||
|
npc.Rogue3,
|
||||||
|
npc.Larva,
|
||||||
|
npc.Familiar,
|
||||||
|
npc.Act3Male,
|
||||||
|
npc.ClayGolem,
|
||||||
|
npc.BloodGolem,
|
||||||
|
npc.IronGolem,
|
||||||
|
npc.FireGolem,
|
||||||
|
npc.Act3Female,
|
||||||
|
npc.Snake,
|
||||||
|
npc.Parrot,
|
||||||
|
npc.Fish,
|
||||||
|
npc.EvilHole,
|
||||||
|
npc.EvilHole2,
|
||||||
|
npc.EvilHole3,
|
||||||
|
npc.EvilHole4,
|
||||||
|
npc.EvilHole5,
|
||||||
|
npc.FireboltTrap,
|
||||||
|
npc.HorzMissileTrap,
|
||||||
|
npc.VertMissileTrap,
|
||||||
|
npc.PoisonCloudTrap,
|
||||||
|
npc.LightningTrap,
|
||||||
|
npc.InvisoSpawner,
|
||||||
|
//338, //Guard
|
||||||
|
npc.MiniSper,
|
||||||
|
npc.BoneWall,
|
||||||
|
npc.Hydra,
|
||||||
|
npc.Hydra2,
|
||||||
|
npc.Hydra3,
|
||||||
|
npc.SevenTombs,
|
||||||
|
npc.Valkyrie,
|
||||||
|
npc.IronWolf,
|
||||||
|
npc.NecroSkeleton,
|
||||||
|
npc.NecroMage,
|
||||||
|
npc.CompellingOrbNpc,
|
||||||
|
npc.SpiritMummy,
|
||||||
|
npc.Act2Guard4,
|
||||||
|
npc.Act2Guard5,
|
||||||
|
npc.Window,
|
||||||
|
npc.Window2,
|
||||||
|
npc.MephistoSpirit,
|
||||||
|
npc.WakeOfDestruction,
|
||||||
|
npc.ChargedBoltSentry,
|
||||||
|
npc.LightningSentry,
|
||||||
|
npc.InvisiblePet,
|
||||||
|
npc.InfernoSentry,
|
||||||
|
npc.DeathSentry,
|
||||||
|
npc.ShadowWarrior,
|
||||||
|
npc.ShadowMaster,
|
||||||
|
npc.DruHawk,
|
||||||
|
npc.DruSpiritWolf,
|
||||||
|
npc.DruFenris,
|
||||||
|
npc.HeartOfWolverine,
|
||||||
|
npc.OakSage,
|
||||||
|
npc.DruBear,
|
||||||
|
npc.BaalThrone,
|
||||||
|
npc.InjuredBarbarian,
|
||||||
|
npc.InjuredBarbarian2,
|
||||||
|
npc.InjuredBarbarian3,
|
||||||
|
npc.DemonHole:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
59
pkg/memory/object.go
Normal file
59
pkg/memory/object.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/object"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gd *GameReader) Objects(playerPosition data.Position) []data.Object {
|
||||||
|
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||||
|
|
||||||
|
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024)
|
||||||
|
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||||
|
|
||||||
|
var objects []data.Object
|
||||||
|
for i := 0; i < 128; i++ {
|
||||||
|
objectOffset := 8 * i
|
||||||
|
objectUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(objectOffset), Uint64))
|
||||||
|
for objectUnitPtr > 0 {
|
||||||
|
objectType := gd.Process.ReadUInt(objectUnitPtr+0x00, Uint32)
|
||||||
|
if objectType == 2 {
|
||||||
|
txtFileNo := gd.Process.ReadUInt(objectUnitPtr+0x04, Uint32)
|
||||||
|
mode := gd.Process.ReadUInt(objectUnitPtr+0x0c, Uint32)
|
||||||
|
unitID := gd.Process.ReadUInt(objectUnitPtr+0x08, Uint32)
|
||||||
|
|
||||||
|
// Coordinates (X, Y)
|
||||||
|
pathPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x38, Uint64))
|
||||||
|
posX := gd.Process.ReadUInt(pathPtr+0x10, Uint16)
|
||||||
|
posY := gd.Process.ReadUInt(pathPtr+0x14, Uint16)
|
||||||
|
|
||||||
|
unitDataPtr := uintptr(gd.Process.ReadUInt(objectUnitPtr+0x10, Uint64))
|
||||||
|
interactType := gd.Process.ReadUInt(unitDataPtr+0x08, Uint8)
|
||||||
|
|
||||||
|
obj := data.Object{
|
||||||
|
Name: object.Name(int(txtFileNo)),
|
||||||
|
IsHovered: unitID == hoveredUnitID && hoveredType == 2 && isHovered,
|
||||||
|
InteractType: object.InteractType(interactType),
|
||||||
|
Selectable: mode == 0,
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(posX),
|
||||||
|
Y: int(posY),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
objects = append(objects, obj)
|
||||||
|
}
|
||||||
|
objectUnitPtr = uintptr(gd.Process.ReadUInt(objectUnitPtr+0x150, Uint64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(objects, func(i, j int) bool {
|
||||||
|
distanceI := utils.DistanceFromPoint(playerPosition, objects[i].Position)
|
||||||
|
distanceJ := utils.DistanceFromPoint(playerPosition, objects[j].Position)
|
||||||
|
|
||||||
|
return distanceI < distanceJ
|
||||||
|
})
|
||||||
|
|
||||||
|
return objects
|
||||||
|
}
|
||||||
56
pkg/memory/offset.go
Normal file
56
pkg/memory/offset.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
type Offset struct {
|
||||||
|
GameData uintptr
|
||||||
|
UnitTable uintptr
|
||||||
|
UI uintptr
|
||||||
|
Hover uintptr
|
||||||
|
Expansion uintptr
|
||||||
|
RosterOffset uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateOffsets(process Process) Offset {
|
||||||
|
// ignoring errors, always best practices
|
||||||
|
memory, _ := process.getProcessMemory()
|
||||||
|
|
||||||
|
// GameReader
|
||||||
|
pattern := process.FindPattern(memory, "\x44\x88\x25\x00\x00\x00\x00\x66\x44\x89\x25\x00\x00\x00\x00", "xxx????xxxx????")
|
||||||
|
bytes := process.ReadBytesFromMemory(pattern+0x3, 4)
|
||||||
|
offsetInt := uintptr(binary.LittleEndian.Uint32(bytes))
|
||||||
|
gameDataOffset := (pattern - process.moduleBaseAddressPtr) - 0x121 + offsetInt
|
||||||
|
|
||||||
|
// UnitTable
|
||||||
|
pattern = process.FindPattern(memory, "\x48\x03\xC7\x49\x8B\x8C\xC6", "xxxxxxx")
|
||||||
|
bytes = process.ReadBytesFromMemory(pattern+7, 4)
|
||||||
|
unitTableOffset := uintptr(binary.LittleEndian.Uint32(bytes))
|
||||||
|
|
||||||
|
// UI
|
||||||
|
pattern = process.FindPattern(memory, "\x40\x84\xed\x0f\x94\x05", "xxxxxx")
|
||||||
|
uiOffset := process.ReadUInt(pattern+6, Uint32)
|
||||||
|
uiOffsetPtr := (pattern - process.moduleBaseAddressPtr) + 10 + uintptr(uiOffset)
|
||||||
|
|
||||||
|
// Hover
|
||||||
|
pattern = process.FindPattern(memory, "\xc6\x84\xc2\x00\x00\x00\x00\x00\x48\x8b\x74\x24\x00", "xxx?????xxxx?")
|
||||||
|
hoverOffset := process.ReadUInt(pattern+3, Uint32) - 1
|
||||||
|
|
||||||
|
// Expansion
|
||||||
|
pattern = process.FindPattern(memory, "\x48\x8B\x05\x00\x00\x00\x00\x48\x8B\xD9\xF3\x0F\x10\x50\x00", "xxx????xxxxxxx?")
|
||||||
|
offsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32))
|
||||||
|
expOffset := pattern - process.moduleBaseAddressPtr + 7 + offsetPtr
|
||||||
|
|
||||||
|
// Party members offset
|
||||||
|
pattern = process.FindPattern(memory, "\x02\x45\x33\xD2\x4D\x8B", "xxxxxx")
|
||||||
|
offsetPtr = uintptr(process.ReadUInt(pattern-3, Uint32))
|
||||||
|
rosterOffset := pattern - process.moduleBaseAddressPtr + 1 + offsetPtr
|
||||||
|
|
||||||
|
return Offset{
|
||||||
|
GameData: gameDataOffset,
|
||||||
|
UnitTable: unitTableOffset,
|
||||||
|
UI: uiOffsetPtr,
|
||||||
|
Hover: uintptr(hoverOffset),
|
||||||
|
Expansion: expOffset,
|
||||||
|
RosterOffset: rosterOffset,
|
||||||
|
}
|
||||||
|
}
|
||||||
271
pkg/memory/player.go
Normal file
271
pkg/memory/player.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/skill"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gd *GameReader) getPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintptr, corpse data.Corpse) {
|
||||||
|
for i := 0; i < 128; i++ {
|
||||||
|
unitOffset := gd.offset.UnitTable + uintptr(i*8)
|
||||||
|
playerUnitAddr := gd.Process.moduleBaseAddressPtr + unitOffset
|
||||||
|
playerUnit := uintptr(gd.Process.ReadUInt(playerUnitAddr, Uint64))
|
||||||
|
for playerUnit > 0 {
|
||||||
|
pInventory := playerUnit + 0x90
|
||||||
|
inventoryAddr := uintptr(gd.Process.ReadUInt(pInventory, Uint64))
|
||||||
|
|
||||||
|
pPath := playerUnit + 0x38
|
||||||
|
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
||||||
|
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
||||||
|
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
||||||
|
|
||||||
|
// Only current player has inventory
|
||||||
|
if inventoryAddr > 0 && xPos > 0 && yPos > 0 {
|
||||||
|
expCharPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.Expansion, Uint64))
|
||||||
|
expChar := gd.Process.ReadUInt(expCharPtr+0x5C, Uint16)
|
||||||
|
baseCheck := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16)
|
||||||
|
if expChar > 0 {
|
||||||
|
baseCheck = gd.Process.ReadUInt(inventoryAddr+0x70, Uint16)
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseCheck > 0 {
|
||||||
|
isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8)
|
||||||
|
|
||||||
|
if isCorpse == 1 {
|
||||||
|
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
||||||
|
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||||
|
corpse = data.Corpse{
|
||||||
|
Found: true,
|
||||||
|
IsHovered: isHovered && hoveredUnitID == unitID && hoveredType == 0,
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(xPos),
|
||||||
|
Y: int(yPos),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playerUnitPtr = playerUnit
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pUnitData := playerUnit + 0x10
|
||||||
|
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
||||||
|
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
||||||
|
for k, rm := range roster {
|
||||||
|
if name != rm.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
roster[k] = data.RosterMember{
|
||||||
|
Name: name,
|
||||||
|
Area: rm.Area,
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(xPos),
|
||||||
|
Y: int(yPos),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerUnit = uintptr(gd.Process.ReadUInt(playerUnit+0x150, Uint64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
||||||
|
// Read X and Y Positions
|
||||||
|
pPath := playerUnit + 0x38
|
||||||
|
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
||||||
|
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
||||||
|
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
||||||
|
|
||||||
|
// Player name
|
||||||
|
pUnitData := playerUnit + 0x10
|
||||||
|
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
||||||
|
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
||||||
|
|
||||||
|
// Get Stats
|
||||||
|
statsListExPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x88, Uint64))
|
||||||
|
statPtr := gd.Process.ReadUInt(statsListExPtr+0x30, Uint64)
|
||||||
|
statCount := gd.Process.ReadUInt(statsListExPtr+0x38, Uint64)
|
||||||
|
|
||||||
|
stats := map[stat.Stat]int{}
|
||||||
|
for j := 0; j < int(statCount); j++ {
|
||||||
|
statOffset := uintptr(statPtr) + 0x2 + uintptr(j*8)
|
||||||
|
statNumber := gd.Process.ReadUInt(statOffset, Uint16)
|
||||||
|
statValue := gd.Process.ReadUInt(statOffset+0x02, Uint32)
|
||||||
|
|
||||||
|
switch stat.Stat(statNumber) {
|
||||||
|
case stat.Life,
|
||||||
|
stat.MaxLife,
|
||||||
|
stat.Mana,
|
||||||
|
stat.MaxMana:
|
||||||
|
stats[stat.Stat(statNumber)] = int(uint32(statValue) >> 8)
|
||||||
|
default:
|
||||||
|
stats[stat.Stat(statNumber)] = int(statValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// States (Buff, Debuff, Auras)
|
||||||
|
states := gd.getStates(statsListExPtr)
|
||||||
|
|
||||||
|
// Skills
|
||||||
|
skills := gd.getSkills(playerUnit + 0x100)
|
||||||
|
|
||||||
|
// Level number
|
||||||
|
pathPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x38, Uint64))
|
||||||
|
room1Ptr := uintptr(gd.Process.ReadUInt(pathPtr+0x20, Uint64))
|
||||||
|
room2Ptr := uintptr(gd.Process.ReadUInt(room1Ptr+0x18, Uint64))
|
||||||
|
levelPtr := uintptr(gd.Process.ReadUInt(room2Ptr+0x90, Uint64))
|
||||||
|
levelNo := gd.Process.ReadUInt(levelPtr+0x1F8, Uint32)
|
||||||
|
|
||||||
|
return data.PlayerUnit{
|
||||||
|
Name: name,
|
||||||
|
Area: area.Area(levelNo),
|
||||||
|
Position: data.Position{
|
||||||
|
X: int(xPos),
|
||||||
|
Y: int(yPos),
|
||||||
|
},
|
||||||
|
Stats: stats,
|
||||||
|
Skills: skills,
|
||||||
|
States: states,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) getSkills(skillsPtr uintptr) map[skill.Skill]int {
|
||||||
|
skills := make(map[skill.Skill]int)
|
||||||
|
skillListPtr := uintptr(gd.Process.ReadUInt(skillsPtr, Uint64))
|
||||||
|
|
||||||
|
skillPtr := uintptr(gd.Process.ReadUInt(skillListPtr, Uint64))
|
||||||
|
|
||||||
|
for skillPtr != 0 {
|
||||||
|
skillTxtPtr := uintptr(gd.Process.ReadUInt(skillPtr, Uint64))
|
||||||
|
skillTxt := uintptr(gd.Process.ReadUInt(skillTxtPtr, Uint16))
|
||||||
|
skillLvl := gd.Process.ReadUInt(skillTxtPtr+0x34, Uint16)
|
||||||
|
|
||||||
|
skills[skill.Skill(skillTxt)] = int(skillLvl)
|
||||||
|
|
||||||
|
skillPtr = uintptr(gd.Process.ReadUInt(skillPtr+0x08, Uint64))
|
||||||
|
}
|
||||||
|
|
||||||
|
return skills
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GameReader) getStates(statsListExPtr uintptr) []state.State {
|
||||||
|
var states []state.State
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
offset := i * 4
|
||||||
|
stateByte := gd.Process.ReadUInt(statsListExPtr+0xAD0+uintptr(offset), Uint32)
|
||||||
|
|
||||||
|
offset = (32 * i) - 1
|
||||||
|
states = append(states, calculateStates(stateByte, uint(offset))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateStates(stateFlag uint, offset uint) []state.State {
|
||||||
|
var states []state.State
|
||||||
|
if 0x00000001&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(1+offset))
|
||||||
|
}
|
||||||
|
if 0x00000002&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(2+offset))
|
||||||
|
}
|
||||||
|
if 0x00000004&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(3+offset))
|
||||||
|
}
|
||||||
|
if 0x00000008&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(4+offset))
|
||||||
|
}
|
||||||
|
if 0x00000010&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(5+offset))
|
||||||
|
}
|
||||||
|
if 0x00000020&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(6+offset))
|
||||||
|
}
|
||||||
|
if 0x00000040&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(7+offset))
|
||||||
|
}
|
||||||
|
if 0x00000080&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(8+offset))
|
||||||
|
}
|
||||||
|
if 0x00000100&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(9+offset))
|
||||||
|
}
|
||||||
|
if 0x00000200&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(10+offset))
|
||||||
|
}
|
||||||
|
if 0x00000400&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(11+offset))
|
||||||
|
}
|
||||||
|
if 0x00000800&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(12+offset))
|
||||||
|
}
|
||||||
|
if 0x00001000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(13+offset))
|
||||||
|
}
|
||||||
|
if 0x00002000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(14+offset))
|
||||||
|
}
|
||||||
|
if 0x00004000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(15+offset))
|
||||||
|
}
|
||||||
|
if 0x00008000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(16+offset))
|
||||||
|
}
|
||||||
|
if 0x00010000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(17+offset))
|
||||||
|
}
|
||||||
|
if 0x00020000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(18+offset))
|
||||||
|
}
|
||||||
|
if 0x00040000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(19+offset))
|
||||||
|
}
|
||||||
|
if 0x00080000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(20+offset))
|
||||||
|
}
|
||||||
|
if 0x00100000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(21+offset))
|
||||||
|
}
|
||||||
|
if 0x00200000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(22+offset))
|
||||||
|
}
|
||||||
|
if 0x00400000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(23+offset))
|
||||||
|
}
|
||||||
|
if 0x00800000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(24+offset))
|
||||||
|
}
|
||||||
|
if 0x01000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(25+offset))
|
||||||
|
}
|
||||||
|
if 0x02000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(26+offset))
|
||||||
|
}
|
||||||
|
if 0x04000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(27+offset))
|
||||||
|
}
|
||||||
|
if 0x08000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(28+offset))
|
||||||
|
}
|
||||||
|
if 0x10000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(29+offset))
|
||||||
|
}
|
||||||
|
if 0x20000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(30+offset))
|
||||||
|
}
|
||||||
|
if 0x40000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(31+offset))
|
||||||
|
}
|
||||||
|
if 0x80000000&stateFlag != 0 {
|
||||||
|
states = append(states, state.State(32+offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
return states
|
||||||
|
}
|
||||||
166
pkg/memory/process.go
Normal file
166
pkg/memory/process.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/winlabs/gowin32"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const moduleName = "D2R.exe"
|
||||||
|
|
||||||
|
type Process struct {
|
||||||
|
handler windows.Handle
|
||||||
|
pid uint
|
||||||
|
moduleBaseAddressPtr uintptr
|
||||||
|
moduleBaseSize uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProcess() (Process, error) {
|
||||||
|
module, err := getGameModule()
|
||||||
|
if err != nil {
|
||||||
|
return Process{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := windows.OpenProcess(0x0010, false, uint32(module.ProcessID))
|
||||||
|
if err != nil {
|
||||||
|
return Process{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Process{
|
||||||
|
handler: h,
|
||||||
|
pid: module.ProcessID,
|
||||||
|
moduleBaseAddressPtr: uintptr(unsafe.Pointer(module.ModuleBaseAddress)),
|
||||||
|
moduleBaseSize: module.ModuleBaseSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGameModule() (gowin32.ModuleInfo, error) {
|
||||||
|
processes := make([]uint32, 2048)
|
||||||
|
length := uint32(0)
|
||||||
|
err := windows.EnumProcesses(processes, &length)
|
||||||
|
if err != nil {
|
||||||
|
return gowin32.ModuleInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, process := range processes {
|
||||||
|
module, _ := getMainModule(process)
|
||||||
|
|
||||||
|
if strings.Contains(module.ExePath, moduleName) {
|
||||||
|
return module, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gowin32.ModuleInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMainModule(pid uint32) (gowin32.ModuleInfo, error) {
|
||||||
|
mi, err := gowin32.GetProcessModules(pid)
|
||||||
|
if err != nil {
|
||||||
|
return gowin32.ModuleInfo{}, err
|
||||||
|
}
|
||||||
|
for _, m := range mi {
|
||||||
|
if m.ModuleName == moduleName {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gowin32.ModuleInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) getProcessMemory() ([]byte, error) {
|
||||||
|
var data = make([]byte, p.moduleBaseSize)
|
||||||
|
err := windows.ReadProcessMemory(p.handler, p.moduleBaseAddressPtr, &data[0], uintptr(p.moduleBaseSize), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) ReadBytesFromMemory(address uintptr, size uint) []byte {
|
||||||
|
var data = make([]byte, size)
|
||||||
|
windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntType uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
Uint8 = 1
|
||||||
|
Uint16 = 2
|
||||||
|
Uint32 = 4
|
||||||
|
Uint64 = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Process) ReadUInt(address uintptr, size IntType) uint {
|
||||||
|
bytes := p.ReadBytesFromMemory(address, uint(size))
|
||||||
|
|
||||||
|
return bytesToUint(bytes, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUIntFromBuffer(bytes []byte, offset uint, size IntType) uint {
|
||||||
|
return bytesToUint(bytes[offset:offset+uint(size)], size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToUint(bytes []byte, size IntType) uint {
|
||||||
|
switch size {
|
||||||
|
case Uint8:
|
||||||
|
return uint(bytes[0])
|
||||||
|
case Uint16:
|
||||||
|
return uint(binary.LittleEndian.Uint16(bytes))
|
||||||
|
case Uint32:
|
||||||
|
return uint(binary.LittleEndian.Uint32(bytes))
|
||||||
|
case Uint64:
|
||||||
|
return uint(binary.LittleEndian.Uint64(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) ReadStringFromMemory(address uintptr, size uint) string {
|
||||||
|
if size == 0 {
|
||||||
|
for i := 1; true; i++ {
|
||||||
|
data := p.ReadBytesFromMemory(address, uint(i))
|
||||||
|
if data[i-1] == 0 {
|
||||||
|
return string(bytes.Trim(data, "\x00"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes.Trim(p.ReadBytesFromMemory(address, size), "\x00"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) findPattern(memory []byte, pattern, mask string) int {
|
||||||
|
patternLength := len(pattern)
|
||||||
|
for i := 0; i < int(p.moduleBaseSize)-patternLength; i++ {
|
||||||
|
found := true
|
||||||
|
for j := 0; j < patternLength; j++ {
|
||||||
|
if string(mask[j]) != "?" && string(pattern[j]) != string(memory[i+j]) {
|
||||||
|
found = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
||||||
|
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||||
|
return p.moduleBaseAddressPtr + uintptr(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Process) GetPID() uint {
|
||||||
|
return p.pid
|
||||||
|
}
|
||||||
27
pkg/memory/roster.go
Normal file
27
pkg/memory/roster.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gd *GameReader) getRoster() (roster []data.RosterMember) {
|
||||||
|
partyStruct := uintptr(gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.RosterOffset, Uint64))
|
||||||
|
|
||||||
|
for partyStruct > 0 {
|
||||||
|
name := gd.Process.ReadStringFromMemory(partyStruct, 16)
|
||||||
|
a := gd.Process.ReadUInt(partyStruct+0x5C, Uint32)
|
||||||
|
|
||||||
|
xPos := gd.Process.ReadUInt(partyStruct+0x60, Uint32)
|
||||||
|
yPos := gd.Process.ReadUInt(partyStruct+0x64, Uint32)
|
||||||
|
|
||||||
|
roster = append(roster, data.RosterMember{
|
||||||
|
Name: name,
|
||||||
|
Area: area.Area(a),
|
||||||
|
Position: data.Position{X: int(xPos), Y: int(yPos)},
|
||||||
|
})
|
||||||
|
partyStruct = uintptr(gd.Process.ReadUInt(partyStruct+0x148, Uint64))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
13
pkg/utils/utils.go
Normal file
13
pkg/utils/utils.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DistanceFromPoint(from data.Position, to data.Position) int {
|
||||||
|
first := math.Pow(float64(to.X-from.X), 2)
|
||||||
|
second := math.Pow(float64(to.Y-from.Y), 2)
|
||||||
|
|
||||||
|
return int(math.Sqrt(first + second))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user