325 lines
10 KiB
Go
325 lines
10 KiB
Go
package memory
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"github.com/hectorgimenez/d2go/pkg/data/mode"
|
|
|
|
"github.com/hectorgimenez/d2go/pkg/data"
|
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
|
"github.com/hectorgimenez/d2go/pkg/data/game"
|
|
"github.com/hectorgimenez/d2go/pkg/data/skill"
|
|
"github.com/hectorgimenez/d2go/pkg/data/state"
|
|
)
|
|
|
|
func (gd *GameReader) GetRawPlayerUnits() RawPlayerUnits {
|
|
rawPlayerUnits := make(RawPlayerUnits, 0)
|
|
hover := gd.HoveredData()
|
|
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 {
|
|
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
|
pInventory := playerUnit + 0x90
|
|
inventoryAddr := uintptr(gd.Process.ReadUInt(pInventory, Uint64))
|
|
|
|
pPath := playerUnit + 0x38
|
|
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
|
room1Ptr := uintptr(gd.Process.ReadUInt(pathAddress+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)
|
|
|
|
xPos := gd.Process.ReadUInt(pathAddress+0x02, Uint16)
|
|
yPos := gd.Process.ReadUInt(pathAddress+0x06, Uint16)
|
|
pUnitData := playerUnit + 0x10
|
|
playerNameAddr := uintptr(gd.Process.ReadUInt(pUnitData, Uint64))
|
|
name := gd.Process.ReadStringFromMemory(playerNameAddr, 0)
|
|
|
|
expCharPtr := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+gd.offset.Expansion, Uint64))
|
|
expChar := gd.Process.ReadUInt(expCharPtr+0x5C, Uint16)
|
|
isMainPlayer := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16)
|
|
if expChar >= uint(game.CharLoD) {
|
|
isMainPlayer = gd.Process.ReadUInt(inventoryAddr+0x70, Uint16)
|
|
}
|
|
isCorpse := gd.Process.ReadUInt(playerUnit+0x1AE, Uint8)
|
|
|
|
statsListExPtr := uintptr(gd.Process.ReadUInt(playerUnit+0x88, Uint64))
|
|
baseStats := gd.getStatsList(statsListExPtr + 0x30)
|
|
stats := gd.getStatsList(statsListExPtr + 0xA8)
|
|
states := gd.GetStates(statsListExPtr)
|
|
playerMode := mode.PlayerMode(gd.Process.ReadUInt(playerUnit+0x0c, Uint32))
|
|
|
|
rawPlayerUnits = append(rawPlayerUnits, RawPlayerUnit{
|
|
UnitID: data.UnitID(unitID),
|
|
Address: playerUnit,
|
|
Name: name,
|
|
IsMainPlayer: isMainPlayer > 0,
|
|
IsCorpse: isCorpse == 1 && inventoryAddr > 0 && xPos > 0 && yPos > 0,
|
|
Area: area.ID(levelNo),
|
|
Position: data.Position{
|
|
X: int(xPos),
|
|
Y: int(yPos),
|
|
},
|
|
IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0,
|
|
States: states,
|
|
Stats: stats,
|
|
BaseStats: baseStats,
|
|
Mode: playerMode,
|
|
})
|
|
playerUnit = uintptr(gd.Process.ReadUInt(playerUnit+0x158, Uint64))
|
|
}
|
|
}
|
|
|
|
return rawPlayerUnits
|
|
}
|
|
|
|
func (gd *GameReader) GetPlayerUnit(mainPlayerUnit RawPlayerUnit) data.PlayerUnit {
|
|
// Skills
|
|
skillListPtr := uintptr(gd.Process.ReadUInt(mainPlayerUnit.Address+0x100, Uint64))
|
|
skills := gd.getSkills(skillListPtr)
|
|
|
|
leftSkillPtr := gd.Process.ReadUInt(skillListPtr+0x08, Uint64)
|
|
leftSkillTxtPtr := uintptr(gd.Process.ReadUInt(uintptr(leftSkillPtr), Uint64))
|
|
leftSkillId := uintptr(gd.Process.ReadUInt(leftSkillTxtPtr, Uint16))
|
|
|
|
rightSkillPtr := gd.Process.ReadUInt(skillListPtr+0x10, Uint64)
|
|
rightSkillTxtPtr := uintptr(gd.Process.ReadUInt(uintptr(rightSkillPtr), Uint64))
|
|
rightSkillId := uintptr(gd.Process.ReadUInt(rightSkillTxtPtr, Uint16))
|
|
|
|
// Class
|
|
class := data.Class(gd.Process.ReadUInt(mainPlayerUnit.Address+0x17C, Uint32))
|
|
|
|
availableWPs := gd.decodeWaypointMasks()
|
|
|
|
d := data.PlayerUnit{
|
|
Address: mainPlayerUnit.Address,
|
|
Name: mainPlayerUnit.Name,
|
|
ID: mainPlayerUnit.UnitID,
|
|
Area: mainPlayerUnit.Area,
|
|
Position: mainPlayerUnit.Position,
|
|
Stats: mainPlayerUnit.Stats,
|
|
BaseStats: mainPlayerUnit.BaseStats,
|
|
Skills: skills,
|
|
States: mainPlayerUnit.States,
|
|
Class: class,
|
|
LeftSkill: skill.ID(leftSkillId),
|
|
RightSkill: skill.ID(rightSkillId),
|
|
AvailableWaypoints: availableWPs,
|
|
Mode: mainPlayerUnit.Mode,
|
|
}
|
|
|
|
return d
|
|
}
|
|
|
|
// WaypointTableData returns the waypoint table struct and the data buffer it points to
|
|
func (gd *GameReader) WaypointTableData() (structAddr uintptr, structBuf []byte, dataAddr uintptr, dataBuf []byte) {
|
|
var structSize = uint(0x100)
|
|
var dataSize = uint(0x200)
|
|
|
|
ptrAddr := gd.moduleBaseAddressPtr + gd.offset.WaypointTableOffset
|
|
structAddr = uintptr(gd.Process.ReadUInt(ptrAddr, Uint64))
|
|
structBuf = gd.Process.ReadBytesFromMemory(structAddr, structSize)
|
|
if len(structBuf) >= 0x18 {
|
|
dataAddr = uintptr(binary.LittleEndian.Uint64(structBuf[0x10:]))
|
|
if dataAddr != 0 {
|
|
dataBuf = gd.Process.ReadBytesFromMemory(dataAddr, dataSize)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// decodeWaypointMasks reads the global waypoint bitfield from the waypoint table.
|
|
// It returns nil if the table cannot be read.
|
|
func (gd *GameReader) decodeWaypointMasks() []area.ID {
|
|
waypointOrder := []area.ID{
|
|
// Act 1
|
|
area.RogueEncampment, area.ColdPlains, area.StonyField, area.DarkWood, area.BlackMarsh,
|
|
area.OuterCloister, area.JailLevel1, area.InnerCloister, area.CatacombsLevel2,
|
|
// Act 2
|
|
area.LutGholein, area.SewersLevel2Act2, area.DryHills, area.HallsOfTheDeadLevel2, area.FarOasis,
|
|
area.LostCity, area.PalaceCellarLevel1, area.ArcaneSanctuary, area.CanyonOfTheMagi,
|
|
// Act 3
|
|
area.KurastDocks, area.SpiderForest, area.GreatMarsh, area.FlayerJungle, area.LowerKurast,
|
|
area.KurastBazaar, area.UpperKurast, area.Travincal, area.DuranceOfHateLevel2,
|
|
// Act 4
|
|
area.ThePandemoniumFortress, area.CityOfTheDamned, area.RiverOfFlame,
|
|
// Act 5
|
|
area.Harrogath, area.FrigidHighlands, area.ArreatPlateau, area.CrystallinePassage, area.GlacialTrail,
|
|
area.HallsOfPain, area.FrozenTundra, area.TheAncientsWay, area.TheWorldStoneKeepLevel2,
|
|
}
|
|
_, structBuf, _, _ := gd.WaypointTableData()
|
|
if len(structBuf) < 4 {
|
|
return nil
|
|
}
|
|
// First two bytes are the 0x0201 header; the next five bytes hold the bitfield (per classic D2 layout).
|
|
if structBuf[0] != 0x02 || structBuf[1] != 0x01 {
|
|
return nil
|
|
}
|
|
|
|
var bits uint64
|
|
for i := 0; i < 5 && 2+i < len(structBuf); i++ {
|
|
bits |= uint64(structBuf[2+i]) << (8 * i)
|
|
}
|
|
|
|
if bits == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]area.ID, 0, len(waypointOrder))
|
|
for idx, wpArea := range waypointOrder {
|
|
if bits&(1<<uint(idx)) != 0 {
|
|
out = append(out, wpArea)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (gd *GameReader) getSkills(skillListPtr uintptr) map[skill.ID]skill.Points {
|
|
skills := make(map[skill.ID]skill.Points)
|
|
|
|
skillPtr := uintptr(gd.Process.ReadUInt(skillListPtr, Uint64))
|
|
|
|
for skillPtr != 0 {
|
|
skillTxtPtr := uintptr(gd.Process.ReadUInt(skillPtr, Uint64))
|
|
skillTxt := uintptr(gd.Process.ReadUInt(skillTxtPtr, Uint16))
|
|
lvl := gd.Process.ReadUInt(skillPtr+0x40, Uint16)
|
|
charges := gd.Process.ReadUInt(skillPtr+0x48, Uint16)
|
|
|
|
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,
|
|
Charges: charges,
|
|
}
|
|
}
|
|
|
|
skillPtr = uintptr(gd.Process.ReadUInt(skillPtr+0x08, Uint64))
|
|
}
|
|
|
|
return skills
|
|
}
|
|
|
|
func (gd *GameReader) GetStates(statsListExPtr uintptr) state.States {
|
|
var states state.States
|
|
for i := 0; i < 6; i++ {
|
|
offset := i * 4
|
|
stateByte := gd.Process.ReadUInt(statsListExPtr+0xAF0+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
|
|
}
|