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/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) gd.ExpChar = uint16(expChar) isMainPlayer := gd.Process.ReadUInt(inventoryAddr+0x30, Uint16) if expChar >= 2 { 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< 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 }