Compare commits
13 Commits
15f204e2ff
...
899bed1b1c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
899bed1b1c | ||
|
|
3219e9c851 | ||
|
|
a52ef1fe63 | ||
|
|
e7e82817c4 | ||
|
|
2a899bdfb2 | ||
|
|
9408f1f980 | ||
|
|
bce4d07041 | ||
|
|
439bf27cb2 | ||
|
|
c2be2c1816 | ||
|
|
63e001982c | ||
|
|
a0a2a67e60 | ||
|
|
deb8c37b55 | ||
|
|
8173a628b2 |
@@ -30,14 +30,16 @@ func (b Belt) GetFirstPotion(potionType PotionType) (Position, bool) {
|
|||||||
|
|
||||||
func (b Belt) Rows() int {
|
func (b Belt) Rows() int {
|
||||||
switch b.Name {
|
switch b.Name {
|
||||||
case "":
|
|
||||||
return 1
|
|
||||||
case "Sash", "LightBelt":
|
case "Sash", "LightBelt":
|
||||||
return 2
|
return 2
|
||||||
case "Belt", "HeavyBelt":
|
case "Belt", "HeavyBelt":
|
||||||
return 3
|
return 3
|
||||||
default:
|
case "PlatedBelt",
|
||||||
|
"DemonhideSash", "SharkskinBelt", "MeshBelt", "BattleBelt", "WarBelt",
|
||||||
|
"SpiderwebSash", "VampirefangBelt", "MithrilCoil", "TrollBelt", "ColossusGirdle":
|
||||||
return 4
|
return 4
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,15 @@ type Type struct {
|
|||||||
func (t Type) IsType(typeName string) bool {
|
func (t Type) IsType(typeName string) bool {
|
||||||
return t.Code == typeName
|
return t.Code == typeName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (itmType Type) IsCharmOrTorch() bool {
|
||||||
|
if itmType.IsType(TypeCharm) ||
|
||||||
|
itmType.IsType(TypeSmallCharm) ||
|
||||||
|
itmType.IsType(TypeMediumCharm) ||
|
||||||
|
itmType.IsType(TypeLargeCharm) ||
|
||||||
|
itmType.IsType(TypeCraftedSunderCharm) ||
|
||||||
|
itmType.IsType(TypeTorch) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,7 +99,8 @@ func (o Object) IsRedPortal() bool {
|
|||||||
o.Name == object.BaalsPortal ||
|
o.Name == object.BaalsPortal ||
|
||||||
o.Name == object.ArcaneSanctuaryPortal ||
|
o.Name == object.ArcaneSanctuaryPortal ||
|
||||||
o.Name == object.LastLastPortal ||
|
o.Name == object.LastLastPortal ||
|
||||||
o.Name == object.HellGate
|
o.Name == object.HellGate ||
|
||||||
|
o.Name == object.SiegeHellGate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Object) IsChest() bool {
|
func (o Object) IsChest() bool {
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ func (i Stats) FindStat(id ID, layer int) (Data, bool) {
|
|||||||
|
|
||||||
return Data{}, false
|
return Data{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i Stats) FindStatIndex(id ID, layer int) (int, bool) {
|
||||||
|
for idx, s := range i {
|
||||||
|
if s.ID == id && s.Layer == layer {
|
||||||
|
return idx, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
func (s ID) String() string {
|
func (s ID) String() string {
|
||||||
return StringStats[s]
|
return StringStats[s]
|
||||||
}
|
}
|
||||||
|
|||||||
279
pkg/memory/direct_syscall.go
Normal file
279
pkg/memory/direct_syscall.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type objectAttributes struct {
|
||||||
|
Length uint32
|
||||||
|
RootDirectory windows.Handle
|
||||||
|
ObjectName uintptr
|
||||||
|
Attributes uint32
|
||||||
|
SecurityDescriptor uintptr
|
||||||
|
SecurityQualityOfService uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientID struct {
|
||||||
|
UniqueProcess uintptr
|
||||||
|
UniqueThread uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dsInitOnce sync.Once
|
||||||
|
dsInitErr error
|
||||||
|
|
||||||
|
ntOpenProcessStub uintptr
|
||||||
|
ntReadVMStub uintptr
|
||||||
|
ntWriteVMStub uintptr
|
||||||
|
ntCloseStub uintptr
|
||||||
|
ntAllocVMStub uintptr
|
||||||
|
ntProtectVMStub uintptr
|
||||||
|
ntFreeVMStub uintptr
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDirectSyscalls() {
|
||||||
|
var err error
|
||||||
|
ntOpenProcessStub, err = buildNtStub("NtOpenProcess")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntReadVMStub, err = buildNtStub("NtReadVirtualMemory")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntWriteVMStub, err = buildNtStub("NtWriteVirtualMemory")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntCloseStub, err = buildNtStub("NtClose")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntAllocVMStub, err = buildNtStub("NtAllocateVirtualMemory")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntProtectVMStub, err = buildNtStub("NtProtectVirtualMemory")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ntFreeVMStub, err = buildNtStub("NtFreeVirtualMemory")
|
||||||
|
if err != nil {
|
||||||
|
dsInitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDirectSyscalls() error {
|
||||||
|
dsInitOnce.Do(initDirectSyscalls)
|
||||||
|
return dsInitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNtStub(name string) (uintptr, error) {
|
||||||
|
ntdll := windows.NewLazySystemDLL("ntdll.dll")
|
||||||
|
proc := ntdll.NewProc(name)
|
||||||
|
if err := ntdll.Load(); err != nil {
|
||||||
|
return 0, fmt.Errorf("load ntdll.dll: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := proc.Addr()
|
||||||
|
if addr == 0 {
|
||||||
|
return 0, fmt.Errorf("%s address is zero", name)
|
||||||
|
}
|
||||||
|
sysID, err := extractSysID(addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("extract %s sysid: %w", name, err)
|
||||||
|
}
|
||||||
|
return makeSyscallStub(sysID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractSysID(procAddr uintptr) (uint32, error) {
|
||||||
|
b := unsafe.Slice((*byte)(unsafe.Pointer(procAddr)), 64)
|
||||||
|
if len(b) < 11 {
|
||||||
|
return 0, errors.New("short Nt stub")
|
||||||
|
}
|
||||||
|
if b[0] != 0x4C || b[1] != 0x8B || b[2] != 0xD1 || b[3] != 0xB8 {
|
||||||
|
return 0, errors.New("unexpected Nt stub prefix")
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for i := 0; i+2 < len(b); i++ {
|
||||||
|
if b[i] == 0x0F && b[i+1] == 0x05 && b[i+2] == 0xC3 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return 0, errors.New("syscall;ret not found")
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint32(b[4:8]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSyscallStub(sysID uint32) (uintptr, error) {
|
||||||
|
code := []byte{0x4C, 0x8B, 0xD1, 0xB8, 0, 0, 0, 0, 0x0F, 0x05, 0xC3}
|
||||||
|
binary.LittleEndian.PutUint32(code[4:8], sysID)
|
||||||
|
mem, err := windows.VirtualAlloc(0, uintptr(len(code)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(mem)), len(code)), code)
|
||||||
|
return mem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntStatusErr(op string, status uintptr) error {
|
||||||
|
return fmt.Errorf("%s failed: NTSTATUS=0x%08X", op, uint32(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntOpenProcess(pid uint32, access uint32) (windows.Handle, error) {
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var h windows.Handle
|
||||||
|
oa := objectAttributes{Length: uint32(unsafe.Sizeof(objectAttributes{}))}
|
||||||
|
cid := clientID{UniqueProcess: uintptr(pid)}
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntOpenProcessStub,
|
||||||
|
uintptr(unsafe.Pointer(&h)),
|
||||||
|
uintptr(access),
|
||||||
|
uintptr(unsafe.Pointer(&oa)),
|
||||||
|
uintptr(unsafe.Pointer(&cid)),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return 0, ntStatusErr("NtOpenProcess", status)
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntReadProcessMemory(handle windows.Handle, address uintptr, out []byte) error {
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var bytesRead uintptr
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntReadVMStub,
|
||||||
|
uintptr(handle),
|
||||||
|
address,
|
||||||
|
uintptr(unsafe.Pointer(&out[0])),
|
||||||
|
uintptr(len(out)),
|
||||||
|
uintptr(unsafe.Pointer(&bytesRead)),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return ntStatusErr("NtReadVirtualMemory", status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntWriteProcessMemory(handle windows.Handle, address uintptr, in []byte) error {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var bytesWritten uintptr
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntWriteVMStub,
|
||||||
|
uintptr(handle),
|
||||||
|
address,
|
||||||
|
uintptr(unsafe.Pointer(&in[0])),
|
||||||
|
uintptr(len(in)),
|
||||||
|
uintptr(unsafe.Pointer(&bytesWritten)),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return ntStatusErr("NtWriteVirtualMemory", status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntCloseHandle(handle windows.Handle) error {
|
||||||
|
if handle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status, _, _ := syscall.SyscallN(ntCloseStub, uintptr(handle))
|
||||||
|
if status != 0 {
|
||||||
|
return ntStatusErr("NtClose", status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntAllocateVirtualMemory(handle windows.Handle, size uintptr, allocType, protect uint32) (uintptr, error) {
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
base := uintptr(0)
|
||||||
|
regionSize := size
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntAllocVMStub,
|
||||||
|
uintptr(handle),
|
||||||
|
uintptr(unsafe.Pointer(&base)),
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(®ionSize)),
|
||||||
|
uintptr(allocType),
|
||||||
|
uintptr(protect),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return 0, ntStatusErr("NtAllocateVirtualMemory", status)
|
||||||
|
}
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntProtectVirtualMemory(handle windows.Handle, address uintptr, size uintptr, newProtect uint32) (uint32, error) {
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
base := address
|
||||||
|
regionSize := size
|
||||||
|
var oldProtect uint32
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntProtectVMStub,
|
||||||
|
uintptr(handle),
|
||||||
|
uintptr(unsafe.Pointer(&base)),
|
||||||
|
uintptr(unsafe.Pointer(®ionSize)),
|
||||||
|
uintptr(newProtect),
|
||||||
|
uintptr(unsafe.Pointer(&oldProtect)),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return 0, ntStatusErr("NtProtectVirtualMemory", status)
|
||||||
|
}
|
||||||
|
return oldProtect, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntFreeVirtualMemory(handle windows.Handle, address uintptr, freeType uint32) error {
|
||||||
|
if address == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ensureDirectSyscalls(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
base := address
|
||||||
|
regionSize := uintptr(0) // required for MEM_RELEASE
|
||||||
|
status, _, _ := syscall.SyscallN(
|
||||||
|
ntFreeVMStub,
|
||||||
|
uintptr(handle),
|
||||||
|
uintptr(unsafe.Pointer(&base)),
|
||||||
|
uintptr(unsafe.Pointer(®ionSize)),
|
||||||
|
uintptr(freeType),
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return ntStatusErr("NtFreeVirtualMemory", status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -82,12 +82,16 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
gd.monstersLastUpdate = now
|
gd.monstersLastUpdate = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always update other critical data
|
||||||
|
corpseUnit := rawPlayerUnits.GetCorpse()
|
||||||
|
roster := gd.getRoster(rawPlayerUnits)
|
||||||
|
|
||||||
// Conditionally update inventory 500ms
|
// Conditionally update inventory 500ms
|
||||||
// Except when hovering over an item
|
// Except when hovering over an item
|
||||||
inventory := gd.cachedInventory
|
inventory := gd.cachedInventory
|
||||||
if now.Sub(gd.inventoryLastUpdate) > 250*time.Millisecond ||
|
if now.Sub(gd.inventoryLastUpdate) > 250*time.Millisecond ||
|
||||||
(hover.IsHovered && hover.UnitType == 4) { // 4 = Item type
|
(hover.IsHovered && hover.UnitType == 4) { // 4 = Item type
|
||||||
inventory = gd.Inventory(rawPlayerUnits, hover)
|
inventory = gd.Inventory(rawPlayerUnits, len(roster) > 1, hover)
|
||||||
gd.cachedInventory = inventory
|
gd.cachedInventory = inventory
|
||||||
gd.inventoryLastUpdate = now
|
gd.inventoryLastUpdate = now
|
||||||
}
|
}
|
||||||
@@ -100,9 +104,6 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
gd.objectsLastUpdate = now
|
gd.objectsLastUpdate = now
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always update other critical data
|
|
||||||
corpseUnit := rawPlayerUnits.GetCorpse()
|
|
||||||
roster := gd.getRoster(rawPlayerUnits)
|
|
||||||
openMenus := gd.OpenMenus()
|
openMenus := gd.OpenMenus()
|
||||||
|
|
||||||
// Quests
|
// Quests
|
||||||
@@ -155,8 +156,9 @@ func (gd *GameReader) GetData() data.Data {
|
|||||||
|
|
||||||
func (gd *GameReader) GetInventory() data.Inventory {
|
func (gd *GameReader) GetInventory() data.Inventory {
|
||||||
rawPlayerUnits := gd.GetRawPlayerUnits()
|
rawPlayerUnits := gd.GetRawPlayerUnits()
|
||||||
|
roster := gd.getRoster(rawPlayerUnits)
|
||||||
hover := gd.HoveredData()
|
hover := gd.HoveredData()
|
||||||
return gd.Inventory(rawPlayerUnits, hover)
|
return gd.Inventory(rawPlayerUnits, len(roster) > 1, hover)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *GameReader) InGame() bool {
|
func (gd *GameReader) InGame() bool {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/hectorgimenez/d2go/pkg/utils"
|
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverData) data.Inventory {
|
func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, partyMode bool, hover data.HoverData) data.Inventory {
|
||||||
mainPlayer := rawPlayerUnits.GetMainPlayer()
|
mainPlayer := rawPlayerUnits.GetMainPlayer()
|
||||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
||||||
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||||
@@ -275,7 +275,7 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
// In party mode / current memory layout:
|
// In party mode / current memory layout:
|
||||||
// shared stash page 1 belongs to the current character.
|
// shared stash page 1 belongs to the current character.
|
||||||
// Other pages are other shared stash containers and should keep raw owner.
|
// Other pages are other shared stash containers and should keep raw owner.
|
||||||
if page == 1 {
|
if !partyMode || page == 1 {
|
||||||
itm.Owner = mainPlayer.UnitID
|
itm.Owner = mainPlayer.UnitID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,8 +493,13 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build final inventory
|
type itemDistance struct {
|
||||||
inventory.AllItems = make([]data.Item, len(allItems))
|
item *data.Item
|
||||||
|
distance int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize items and retain the distance with the item that owns it.
|
||||||
|
sortedItems := make([]itemDistance, len(allItems))
|
||||||
for i, itm := range allItems {
|
for i, itm := range allItems {
|
||||||
baseDesc := itm.Desc()
|
baseDesc := itm.Desc()
|
||||||
maxReq := calculateItemLevelReq(itm, baseDesc)
|
maxReq := calculateItemLevelReq(itm, baseDesc)
|
||||||
@@ -529,20 +534,19 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inventory.AllItems[i] = *itm
|
sortedItems[i] = itemDistance{
|
||||||
|
item: itm,
|
||||||
|
distance: utils.DistanceFromPoint(mainPlayer.Position, itm.Position),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort items by distance if needed, using pre-calculated distances
|
sort.SliceStable(sortedItems, func(i, j int) bool {
|
||||||
if len(inventory.AllItems) > 0 {
|
return sortedItems[i].distance < sortedItems[j].distance
|
||||||
// Pre-calculate distances to avoid recalculating in sort comparisons
|
})
|
||||||
distances := make([]int, len(inventory.AllItems))
|
|
||||||
for i, invItem := range inventory.AllItems {
|
|
||||||
distances[i] = utils.DistanceFromPoint(mainPlayer.Position, invItem.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(inventory.AllItems, func(i, j int) bool {
|
inventory.AllItems = make([]data.Item, len(sortedItems))
|
||||||
return distances[i] < distances[j]
|
for i, itm := range sortedItems {
|
||||||
})
|
inventory.AllItems[i] = *itm.item
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.Belt = belt
|
inventory.Belt = belt
|
||||||
@@ -616,8 +620,8 @@ func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Sta
|
|||||||
|
|
||||||
for _, mStat := range modifierBaseStats {
|
for _, mStat := range modifierBaseStats {
|
||||||
// Update fullStats
|
// Update fullStats
|
||||||
if existingStat, found := fullStats.FindStat(mStat.ID, mStat.Layer); found {
|
if idx, found := fullStats.FindStatIndex(mStat.ID, mStat.Layer); found {
|
||||||
existingStat.Value = mStat.Value
|
fullStats[idx].Value = mStat.Value
|
||||||
} else {
|
} else {
|
||||||
fullStats = append(fullStats, mStat)
|
fullStats = append(fullStats, mStat)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func calculateOffsets(_ *Process) Offset {
|
|||||||
questInfoOffset := uintptr(0x1ED5DB8)
|
questInfoOffset := uintptr(0x1ED5DB8)
|
||||||
|
|
||||||
// Terror Zones
|
// Terror Zones
|
||||||
tzOffset := uintptr(0x25B5578)
|
tzOffset := uintptr(0x25C3B50)
|
||||||
|
|
||||||
// Ping
|
// Ping
|
||||||
pingOffset := uintptr(0x1E0C508)
|
pingOffset := uintptr(0x1E0C508)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func NewProcess() (*Process, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
h, err := ntOpenProcess(module.ProcessID, 0x0010)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func NewProcessForPID(pid uint32) (*Process, error) {
|
|||||||
return nil, errors.New("no module found for the specified PID")
|
return nil, errors.New("no module found for the specified PID")
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
h, err := ntOpenProcess(module.ProcessID, 0x0010)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func NewProcessForPID(pid uint32) (*Process, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) Close() error {
|
func (p *Process) Close() error {
|
||||||
return windows.CloseHandle(p.handler)
|
return ntCloseHandle(p.handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModuleBaseAddress returns the base address of the D2R module.
|
// ModuleBaseAddress returns the base address of the D2R module.
|
||||||
@@ -138,7 +138,7 @@ func ReadMemoryChunked(handle windows.Handle, baseAddress uintptr, size uint32)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to read, but don't fail if it's protected
|
// Try to read, but don't fail if it's protected
|
||||||
err := windows.ReadProcessMemory(handle, address, &data[offset], chunkSize, nil)
|
err := ntReadProcessMemory(handle, address, data[offset:offset+chunkSize])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failedReads++
|
failedReads++
|
||||||
// Fill with zeros and continue (pattern matching will fail gracefully)
|
// Fill with zeros and continue (pattern matching will fail gracefully)
|
||||||
@@ -155,7 +155,7 @@ func ReadMemoryChunked(handle windows.Handle, baseAddress uintptr, size uint32)
|
|||||||
|
|
||||||
func (p *Process) ReadBytesFromMemory(address uintptr, size uint) []byte {
|
func (p *Process) ReadBytesFromMemory(address uintptr, size uint) []byte {
|
||||||
var data = make([]byte, size)
|
var data = make([]byte, size)
|
||||||
windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil)
|
_ = ntReadProcessMemory(p.handler, address, data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -275,11 +275,11 @@ type ModuleInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetProcessModules(processID uint32) ([]ModuleInfo, error) {
|
func GetProcessModules(processID uint32) ([]ModuleInfo, error) {
|
||||||
hProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, processID)
|
hProcess, err := ntOpenProcess(processID, windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(hProcess)
|
defer ntCloseHandle(hProcess)
|
||||||
|
|
||||||
var modules [1024]windows.Handle
|
var modules [1024]windows.Handle
|
||||||
var needed uint32
|
var needed uint32
|
||||||
@@ -323,7 +323,7 @@ func (p *Process) ReadPointer(address uintptr, size int) (uintptr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) ReadIntoBuffer(address uintptr, buffer []byte) error {
|
func (p *Process) ReadIntoBuffer(address uintptr, buffer []byte) error {
|
||||||
return windows.ReadProcessMemory(p.handler, address, &buffer[0], uintptr(len(buffer)), nil)
|
return ntReadProcessMemory(p.handler, address, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadWidgetContainer reads the WidgetContainer structure.
|
// ReadWidgetContainer reads the WidgetContainer structure.
|
||||||
|
|||||||
@@ -38,21 +38,14 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
procVirtualAllocEx = kernel32.NewProc("VirtualAllocEx")
|
|
||||||
procVirtualFreeEx = kernel32.NewProc("VirtualFreeEx")
|
|
||||||
procQueueUserAPC = kernel32.NewProc("QueueUserAPC")
|
procQueueUserAPC = kernel32.NewProc("QueueUserAPC")
|
||||||
procSuspendThread = kernel32.NewProc("SuspendThread")
|
procSuspendThread = kernel32.NewProc("SuspendThread")
|
||||||
procResumeThread = kernel32.NewProc("ResumeThread")
|
procResumeThread = kernel32.NewProc("ResumeThread")
|
||||||
procGetExitCodeThread = kernel32.NewProc("GetExitCodeThread")
|
procGetExitCodeThread = kernel32.NewProc("GetExitCodeThread")
|
||||||
procGetThreadTimes = kernel32.NewProc("GetThreadTimes")
|
procGetThreadTimes = kernel32.NewProc("GetThreadTimes")
|
||||||
procVirtualProtectEx = kernel32.NewProc("VirtualProtectEx")
|
|
||||||
procSetProcessValidCallTargets = kernel32.NewProc("SetProcessValidCallTargets")
|
procSetProcessValidCallTargets = kernel32.NewProc("SetProcessValidCallTargets")
|
||||||
procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread")
|
procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread")
|
||||||
|
|
||||||
ntdll = windows.NewLazySystemDLL("ntdll.dll")
|
|
||||||
procNtGetContextThread = ntdll.NewProc("NtGetContextThread")
|
|
||||||
procNtSetContextThread = ntdll.NewProc("NtSetContextThread")
|
|
||||||
|
|
||||||
d2gsCachedFn uintptr
|
d2gsCachedFn uintptr
|
||||||
d2gsCacheMu sync.RWMutex
|
d2gsCacheMu sync.RWMutex
|
||||||
d2gsCachePID uint32
|
d2gsCachePID uint32
|
||||||
@@ -143,7 +136,7 @@ func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) {
|
|||||||
virtualFreeEx(s.handle, s.stub)
|
virtualFreeEx(s.handle, s.stub)
|
||||||
}
|
}
|
||||||
|
|
||||||
windows.CloseHandle(s.handle)
|
_ = ntCloseHandle(s.handle)
|
||||||
s.handle = 0
|
s.handle = 0
|
||||||
s.processPID = 0
|
s.processPID = 0
|
||||||
s.stub = 0
|
s.stub = 0
|
||||||
@@ -153,7 +146,7 @@ func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) {
|
|||||||
s.fn = 0
|
s.fn = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := windows.OpenProcess(sendPacketProcessAccess, false, pid)
|
h, err := ntOpenProcess(pid, sendPacketProcessAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("open process %d: %w", pid, err)
|
return 0, fmt.Errorf("open process %d: %w", pid, err)
|
||||||
}
|
}
|
||||||
@@ -305,7 +298,7 @@ func (s *sendPacketState) Cleanup() error {
|
|||||||
|
|
||||||
// Close thread handle - the main thread may change between games
|
// Close thread handle - the main thread may change between games
|
||||||
if s.thread != 0 {
|
if s.thread != 0 {
|
||||||
windows.CloseHandle(s.thread)
|
_ = ntCloseHandle(s.thread)
|
||||||
s.thread = 0
|
s.thread = 0
|
||||||
s.threadID = 0
|
s.threadID = 0
|
||||||
}
|
}
|
||||||
@@ -343,12 +336,12 @@ func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Thread is invalid, close and reset
|
// Thread is invalid, close and reset
|
||||||
windows.CloseHandle(s.thread)
|
_ = ntCloseHandle(s.thread)
|
||||||
s.thread = 0
|
s.thread = 0
|
||||||
s.threadID = 0
|
s.threadID = 0
|
||||||
s.processPID = 0
|
s.processPID = 0
|
||||||
} else if s.thread != 0 && s.processPID != p.pid {
|
} else if s.thread != 0 && s.processPID != p.pid {
|
||||||
windows.CloseHandle(s.thread)
|
_ = ntCloseHandle(s.thread)
|
||||||
s.thread = 0
|
s.thread = 0
|
||||||
s.threadID = 0
|
s.threadID = 0
|
||||||
s.processPID = 0
|
s.processPID = 0
|
||||||
@@ -366,7 +359,7 @@ func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error)
|
|||||||
|
|
||||||
// Validate the newly opened thread belongs to the correct process
|
// Validate the newly opened thread belongs to the correct process
|
||||||
if err := validateThreadBelongsToProcess(handle, p.pid); err != nil {
|
if err := validateThreadBelongsToProcess(handle, p.pid); err != nil {
|
||||||
windows.CloseHandle(handle)
|
_ = ntCloseHandle(handle)
|
||||||
return 0, fmt.Errorf("thread %d validation failed: %w", threadID, err)
|
return 0, fmt.Errorf("thread %d validation failed: %w", threadID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +502,7 @@ func findMainThreadID(pid uint32) (uint32, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("CreateToolhelp32Snapshot: %w", err)
|
return 0, fmt.Errorf("CreateToolhelp32Snapshot: %w", err)
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(snapshot)
|
defer ntCloseHandle(snapshot)
|
||||||
|
|
||||||
var entry windows.ThreadEntry32
|
var entry windows.ThreadEntry32
|
||||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||||
@@ -526,7 +519,7 @@ func findMainThreadID(pid uint32) (uint32, error) {
|
|||||||
thread, err := windows.OpenThread(sendPacketThreadAccess, false, entry.ThreadID)
|
thread, err := windows.OpenThread(sendPacketThreadAccess, false, entry.ThreadID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
creationTime, err := getThreadCreationTime(thread)
|
creationTime, err := getThreadCreationTime(thread)
|
||||||
windows.CloseHandle(thread)
|
_ = ntCloseHandle(thread)
|
||||||
|
|
||||||
if err == nil && creationTime < earliestCreationTime {
|
if err == nil && creationTime < earliestCreationTime {
|
||||||
earliestCreationTime = creationTime
|
earliestCreationTime = creationTime
|
||||||
@@ -866,7 +859,7 @@ func (p *Process) waitForPacketCompletion(handle windows.Handle, state *sendPack
|
|||||||
select {
|
select {
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
// Try to read status one last time before failing
|
// Try to read status one last time before failing
|
||||||
if err := windows.ReadProcessMemory(handle, state.meta+sendPacketStatusOffset, &statusBuf[0], 4, nil); err == nil {
|
if err := ntReadProcessMemory(handle, state.meta+sendPacketStatusOffset, statusBuf[:]); err == nil {
|
||||||
status := binary.LittleEndian.Uint32(statusBuf[:])
|
status := binary.LittleEndian.Uint32(statusBuf[:])
|
||||||
if status == 1 {
|
if status == 1 {
|
||||||
return nil
|
return nil
|
||||||
@@ -874,7 +867,7 @@ func (p *Process) waitForPacketCompletion(handle windows.Handle, state *sendPack
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("packet send timeout: APC not processed within %dms", timeoutMs)
|
return fmt.Errorf("packet send timeout: APC not processed within %dms", timeoutMs)
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if err := windows.ReadProcessMemory(handle, state.meta+sendPacketStatusOffset, &statusBuf[0], 4, nil); err != nil {
|
if err := ntReadProcessMemory(handle, state.meta+sendPacketStatusOffset, statusBuf[:]); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
status := binary.LittleEndian.Uint32(statusBuf[:])
|
status := binary.LittleEndian.Uint32(statusBuf[:])
|
||||||
@@ -892,74 +885,26 @@ func writeRemoteMemory(handle windows.Handle, address uintptr, data []byte) erro
|
|||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return windows.WriteProcessMemory(handle, address, &data[0], uintptr(len(data)), nil)
|
return ntWriteProcessMemory(handle, address, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func virtualAllocEx(handle windows.Handle, size uintptr, protect uint32) (uintptr, error) {
|
func virtualAllocEx(handle windows.Handle, size uintptr, protect uint32) (uintptr, error) {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return 0, errors.New("allocation size cannot be zero")
|
return 0, errors.New("allocation size cannot be zero")
|
||||||
}
|
}
|
||||||
|
return ntAllocateVirtualMemory(handle, size, windows.MEM_COMMIT|windows.MEM_RESERVE, protect)
|
||||||
addr, _, err := procVirtualAllocEx.Call(
|
|
||||||
uintptr(handle),
|
|
||||||
0,
|
|
||||||
size,
|
|
||||||
windows.MEM_COMMIT|windows.MEM_RESERVE,
|
|
||||||
uintptr(protect),
|
|
||||||
)
|
|
||||||
if addr == 0 {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return 0, errors.New("VirtualAllocEx failed")
|
|
||||||
}
|
|
||||||
return addr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func virtualFreeEx(handle windows.Handle, address uintptr) error {
|
func virtualFreeEx(handle windows.Handle, address uintptr) error {
|
||||||
if address == 0 {
|
return ntFreeVirtualMemory(handle, address, windows.MEM_RELEASE)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, _, err := procVirtualFreeEx.Call(
|
|
||||||
uintptr(handle),
|
|
||||||
address,
|
|
||||||
0,
|
|
||||||
windows.MEM_RELEASE,
|
|
||||||
)
|
|
||||||
if ret == 0 {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return errors.New("VirtualFreeEx failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func virtualProtectEx(handle windows.Handle, address uintptr, size uintptr, protect uint32) error {
|
func virtualProtectEx(handle windows.Handle, address uintptr, size uintptr, protect uint32) error {
|
||||||
if address == 0 || size == 0 {
|
if address == 0 || size == 0 {
|
||||||
return errors.New("attempt to protect invalid region")
|
return errors.New("attempt to protect invalid region")
|
||||||
}
|
}
|
||||||
|
_, err := ntProtectVirtualMemory(handle, address, size, protect)
|
||||||
if err := procVirtualProtectEx.Find(); err != nil {
|
return err
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldProtect uint32
|
|
||||||
ret, _, err := procVirtualProtectEx.Call(
|
|
||||||
uintptr(handle),
|
|
||||||
address,
|
|
||||||
size,
|
|
||||||
uintptr(protect),
|
|
||||||
uintptr(unsafe.Pointer(&oldProtect)),
|
|
||||||
)
|
|
||||||
if ret == 0 {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("VirtualProtectEx: %w", err)
|
|
||||||
}
|
|
||||||
return errors.New("VirtualProtectEx failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func markCallTargetValid(handle windows.Handle, address uintptr, size uintptr) error {
|
func markCallTargetValid(handle windows.Handle, address uintptr, size uintptr) error {
|
||||||
@@ -1078,26 +1023,3 @@ const CONTEXT_AMD64 = 0x00100000
|
|||||||
const CONTEXT_CONTROL = CONTEXT_AMD64 | 0x0001
|
const CONTEXT_CONTROL = CONTEXT_AMD64 | 0x0001
|
||||||
const CONTEXT_INTEGER = CONTEXT_AMD64 | 0x0002
|
const CONTEXT_INTEGER = CONTEXT_AMD64 | 0x0002
|
||||||
const CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER
|
const CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER
|
||||||
|
|
||||||
func getThreadContext(threadHandle windows.Handle, ctx *CONTEXT64) error {
|
|
||||||
ctx.ContextFlags = CONTEXT_FULL
|
|
||||||
ret, _, err := procNtGetContextThread.Call(
|
|
||||||
uintptr(threadHandle),
|
|
||||||
uintptr(unsafe.Pointer(ctx)),
|
|
||||||
)
|
|
||||||
if ret != 0 {
|
|
||||||
return fmt.Errorf("NtGetContextThread failed: 0x%X, %w", ret, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setThreadContext(threadHandle windows.Handle, ctx *CONTEXT64) error {
|
|
||||||
ret, _, err := procNtSetContextThread.Call(
|
|
||||||
uintptr(threadHandle),
|
|
||||||
uintptr(unsafe.Pointer(ctx)),
|
|
||||||
)
|
|
||||||
if ret != 0 {
|
|
||||||
return fmt.Errorf("NtSetContextThread failed: 0x%X, %w", ret, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -450,6 +450,11 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
|||||||
|
|
||||||
// Special handling for stats not found
|
// Special handling for stats not found
|
||||||
if !statFound {
|
if !statFound {
|
||||||
|
if statName == "allres" {
|
||||||
|
stage2Props[statName] = evaluateAllRes(it)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
isResistStat := strings.Contains(statName, "resist")
|
isResistStat := strings.Contains(statName, "resist")
|
||||||
// Special case: Pure resist-sum rules (like sunders) should fail early if no resists exist
|
// Special case: Pure resist-sum rules (like sunders) should fail early if no resists exist
|
||||||
// But mixed rules (resists OR other stats) should continue to check other conditions
|
// But mixed rules (resists OR other stats) should continue to check other conditions
|
||||||
@@ -579,6 +584,28 @@ func evaluateSkillTabSum(it data.Item) int {
|
|||||||
return evaluatePositiveStatSum(it, stat.AddSkillTab, maxSkillTabLayer)
|
return evaluatePositiveStatSum(it, stat.AddSkillTab, maxSkillTabLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evaluateAllRes(it data.Item) int {
|
||||||
|
resistStats := []stat.ID{
|
||||||
|
stat.FireResist,
|
||||||
|
stat.LightningResist,
|
||||||
|
stat.ColdResist,
|
||||||
|
stat.PoisonResist,
|
||||||
|
}
|
||||||
|
|
||||||
|
lowest := 0
|
||||||
|
for idx, statID := range resistStats {
|
||||||
|
itemStat, found := it.FindStat(statID, 0)
|
||||||
|
if !found {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if idx == 0 || itemStat.Value < lowest {
|
||||||
|
lowest = itemStat.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowest
|
||||||
|
}
|
||||||
|
|
||||||
const aggregateLayerFastPathLimit = 64
|
const aggregateLayerFastPathLimit = 64
|
||||||
|
|
||||||
func evaluatePositiveStatSum(it data.Item, statID stat.ID, maxLayer int) int {
|
func evaluatePositiveStatSum(it data.Item, statID stat.ID, maxLayer int) int {
|
||||||
|
|||||||
Reference in New Issue
Block a user