diff --git a/pkg/memory/offset.go b/pkg/memory/offset.go index 1503b5b..9647626 100644 --- a/pkg/memory/offset.go +++ b/pkg/memory/offset.go @@ -13,7 +13,7 @@ type Offset struct { RosterOffset uintptr PanelManagerContainerOffset uintptr WidgetStatesOffset uintptr - WaypointsOffset uintptr + WaypointTableOffset uintptr FPS uintptr KeyBindingsOffset uintptr KeyBindingsSkillsOffset uintptr @@ -72,7 +72,7 @@ func calculateOffsets(process *Process) Offset { // Waypoints pattern = process.FindPattern(memory, "\x48\x89\x05\x00\x00\x00\x00\x0F\x11\x00", "xxx????xxx") offsetBuffer := process.ReadUInt(pattern+3, Uint32) - WaypointsOffset := pattern - process.moduleBaseAddressPtr + 23 + uintptr(offsetBuffer) + WaypointTableOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(offsetBuffer) // FPS pattern = process.FindPattern(memory, "\x8B\x1D\x00\x00\x00\x00\x48\x8D\x05\x00\x00\x00\x00\x48\x8D\x4C\x24\x40", "xx????xxx????xxxxx") @@ -134,7 +134,7 @@ func calculateOffsets(process *Process) Offset { RosterOffset: rosterOffset, PanelManagerContainerOffset: panelManagerContainerOffset, WidgetStatesOffset: WidgetStatesOffset, - WaypointsOffset: WaypointsOffset, + WaypointTableOffset: WaypointTableOffset, FPS: fpsOffset, KeyBindingsOffset: keyBindingsOffset, KeyBindingsSkillsOffset: keyBindingsSkillsOffset, diff --git a/pkg/memory/player.go b/pkg/memory/player.go index 16d1b9f..d34fdc0 100644 --- a/pkg/memory/player.go +++ b/pkg/memory/player.go @@ -90,16 +90,7 @@ func (gd *GameReader) GetPlayerUnit(mainPlayerUnit RawPlayerUnit) data.PlayerUni // Class class := data.Class(gd.Process.ReadUInt(mainPlayerUnit.Address+0x17C, Uint32)) - availableWPs := make([]area.ID, 0) - // Probably there is a better place to pick up those values, since this seems to be very tied to the UI - wpList := gd.Process.ReadBytesFromMemory(gd.moduleBaseAddressPtr+gd.offset.WaypointsOffset, 0x48) - for i := 0; i < 0x48; i = i + 8 { - a := binary.LittleEndian.Uint32(wpList[i : i+4]) - available := binary.LittleEndian.Uint32(wpList[i+4 : i+8]) - if available == 1 || mainPlayerUnit.Area == area.ID(a) { - availableWPs = append(availableWPs, area.ID(a)) - } - } + availableWPs := gd.decodeWaypointMasks() d := data.PlayerUnit{ Address: mainPlayerUnit.Address, @@ -121,6 +112,69 @@ func (gd *GameReader) GetPlayerUnit(mainPlayerUnit RawPlayerUnit) data.PlayerUni 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<