Files
d2go/pkg/memory/game_reader.go

192 lines
5.3 KiB
Go

package memory
import (
"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/stat"
)
type GameReader struct {
offset Offset
previousRead data.Data
Process
}
func NewGameReader(process Process) *GameReader {
return &GameReader{
offset: calculateOffsets(process),
Process: process,
}
}
func (gd *GameReader) GetData() data.Data {
if gd.offset.UnitTable == 0 {
gd.offset = calculateOffsets(gd.Process)
}
rawPlayerUnits := gd.GetRawPlayerUnits()
roster := gd.getRoster(rawPlayerUnits)
mainPlayerUnit := rawPlayerUnits.GetMainPlayer()
if mainPlayerUnit.Address == 0 {
return gd.previousRead
}
pu := gd.GetPlayerUnit(mainPlayerUnit)
hover := gd.hoveredData()
// Quests
q1 := uintptr(gd.Process.ReadUInt(gd.moduleBaseAddressPtr+0x230E9A8, Uint64))
q2 := uintptr(gd.Process.ReadUInt(q1, Uint64))
gameQuestsBytes := gd.Process.ReadBytesFromMemory(q2, 85)
gameQuestsBytes = gameQuestsBytes[3:]
corpseUnit := rawPlayerUnits.GetCorpse()
d := data.Data{
Corpse: data.Corpse{
Found: corpseUnit.Address != 0,
IsHovered: corpseUnit.IsHovered,
Position: corpseUnit.Position,
},
Monsters: gd.Monsters(pu.Position, hover),
PlayerUnit: pu,
Items: gd.Items(rawPlayerUnits, hover),
Objects: gd.Objects(pu.Position, hover),
OpenMenus: gd.openMenus(),
Roster: roster,
HoverData: hover,
TerrorZones: gd.TerrorZones(),
Quests: gd.getQuests(gameQuestsBytes),
KeyBindings: gd.GetKeyBindings(),
}
gd.previousRead = d
return d
}
func (gd *GameReader) InGame() bool {
playerUnits := gd.GetRawPlayerUnits()
return playerUnits.GetMainPlayer().UnitID > 0
}
func (gd *GameReader) openMenus() data.OpenMenus {
uiBase := gd.Process.moduleBaseAddressPtr + gd.offset.UI - 0xA
buffer := gd.Process.ReadBytesFromMemory(uiBase, 0x16D)
isMapShown := gd.Process.ReadUInt(gd.Process.moduleBaseAddressPtr+gd.offset.UI, Uint8)
return data.OpenMenus{
Inventory: buffer[0x01] != 0,
LoadingScreen: buffer[0x16C] != 0,
NPCInteract: buffer[0x08] != 0,
NPCShop: buffer[0x0B] != 0,
Stash: buffer[0x18] != 0,
Waypoint: buffer[0x13] != 0,
MapShown: isMapShown != 0,
SkillTree: buffer[0x04] != 0,
Character: buffer[0x02] != 0,
QuitMenu: buffer[0x09] != 0,
Cube: buffer[0x19] != 0,
SkillSelect: buffer[0x03] != 0,
Anvil: buffer[0x0D] != 0,
}
}
func (gd *GameReader) hoveredData() data.HoverData {
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 data.HoverData{
IsHovered: true,
UnitID: data.UnitID(hoveredUnitID),
UnitType: int(hoveredType),
}
}
return data.HoverData{}
}
func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
statsListBuffer := gd.ReadBytesFromMemory(statListPtr, 0x10)
statList := ReadUIntFromBuffer(statsListBuffer, 0, Uint64)
statCount := ReadUIntFromBuffer(statsListBuffer, 0x08, Uint64)
if statCount == 0 {
return []stat.Data{}
}
var stats = make([]stat.Data, 0)
statBuffer := gd.Process.ReadBytesFromMemory(uintptr(statList), statCount*10)
for i := 0; i < int(statCount); i++ {
offset := uint(i * 8)
statLayer := ReadUIntFromBuffer(statBuffer, offset, Uint16)
statEnum := ReadUIntFromBuffer(statBuffer, offset+0x2, Uint16)
statValue := ReadUIntFromBuffer(statBuffer, offset+0x4, Uint32)
value := int(statValue)
switch stat.ID(statEnum) {
case stat.Life,
stat.MaxLife,
stat.Mana,
stat.MaxMana,
stat.Stamina,
stat.MaxStamina:
value = int(statValue >> 8)
case stat.ColdLength,
stat.PoisonLength:
value = int(statValue / 25)
case stat.DeadlyStrikePerLevel:
value = int(float64(statValue) / .8)
case stat.HitCausesMonsterToFlee:
value = int(float64(statValue) / 1.28)
case stat.AttackRatingUndeadPerLevel:
value = int(statValue / 2)
case stat.MagicFindPerLevel,
stat.ExtraGoldPerLevel,
stat.DamageDemonPerLevel,
stat.DamageUndeadPerLevel,
stat.DefensePerLevel,
stat.MaxDamagePerLevel,
stat.MaxDamagePercentPerLevel,
stat.StrengthPerLevel,
stat.DexterityPerLevel,
stat.VitalityPerLevel,
stat.ThornsPerLevel:
value = int(statValue / 8)
case stat.LifePerLevel,
stat.ManaPerLevel:
value = int(statValue / 2048)
case stat.ReplenishDurability, stat.ReplenishQuantity:
value = 2 / int(statValue)
case stat.RegenStaminaPerLevel:
value = int(statValue) * 10
case stat.LevelRequirePercent:
value = int(statValue) * -1
}
stats = append(stats, stat.Data{
ID: stat.ID(statEnum),
Value: value,
Layer: int(statLayer),
})
}
return stats
}
// TODO: Take a look to better ways to get this data, now it's very flakky, is just a random memory position + not in game
func (gd *GameReader) InCharacterSelectionScreen() bool {
uiBase := gd.Process.moduleBaseAddressPtr + gd.offset.UI - 0xA
return gd.Process.ReadUInt(uiBase, Uint8) != 1 && gd.Process.ReadUInt(gd.moduleBaseAddressPtr+0x1EC5AA8, Uint64) == 0
}
func (gd *GameReader) GetSelectedCharacterName() string {
return gd.Process.ReadStringFromMemory(gd.Process.moduleBaseAddressPtr+0x21A2374, 0)
}