- PVSM.RU - https://www.pvsm.ru -
Привет, %username%! Итак, продолжим написание нашего бота. Из прошлых статей, мы научились находить адрес перехватываемой функции для DirectX 9 и 11, а так же исполнять произвольный ассемблерный код в главном потоке игры и прятать от различных методов защиты. Теперь все эти знания можно применить в реальных боевых условиях. И начнем мы с исследования программы, для которой мы и пишем бот.
Для тех кто пропустил прошлые статьи вот содержание, а кто все прочел идем дальше:
Перед тем как начать, можете поставить на закачку World of Warcraft 5.4.x, для начала, нам понадобиться только Wow.exe, так что можете качать только его. А теперь я Вас погружу в святая святых всех читеров и ботоводов игры World of Warcraft.
Начну совсем из далека. У каждой программы есть алгоритм по которому она работает и память в которой она хранит данные, другими словами — есть код, а есть данные иногда данные бывают и в самом коде. Так во для того что бы иметь представление об окружающем мире в игре, нам необходимы именно данные. Что бы их получить, рассмотрим как они хранятся в памяти и какие они бывают. Итак, введем понятие игрового объекта (в дальнейшем WowObject) — это базовый объект, который хранится в памяти и имеет такие свойства Descriptors, ObjectType и Guid, вот его структура:
[StructLayout(LayoutKind.Sequential)]
struct WowObjStruct
{
IntPtr vtable; // 0x00
public IntPtr Descriptors; // 0x4
IntPtr unk1; // 0x8
public int ObjectType; // 0xC
int unk3; // 0x10
IntPtr unk4; // 0x14
IntPtr unk5; // 0x18
IntPtr unk6; // 0x1C
IntPtr unk7; // 0x20
IntPtr unk8; // 0x24
public ulong Guid; // 0x28
}
где Guid — уникальное значение объекта в игре, OjectType — тип объекта в игре и может принимать следующие значения:
public enum WoWObjectType : int
{
Object = 0,
Item = 1,
Container = 2,
Unit = 3,
Player = 4,
GameObject = 5,
DynamicObject = 6,
Corpse = 7,
AreaTrigger = 8,
SceneObject = 9,
NumClientObjectTypes = 0xA,
None = 0x270f,
}
[Flags]
public enum WoWObjectTypeFlags
{
Object = 1 << WoWObjectType.Object,
Item = 1 << WoWObjectType.Item,
Container = 1 << WoWObjectType.Container,
Unit = 1 << WoWObjectType.Unit,
Player = 1 << WoWObjectType.Player,
GameObject = 1 << WoWObjectType.GameObject,
DynamicObject = 1 << WoWObjectType.DynamicObject,
Corpse = 1 << WoWObjectType.Corpse,
AreaTrigger = 1 << WoWObjectType.AreaTrigger,
SceneObject = 1 << WoWObjectType.SceneObject
}
а Descriptors — это указатель на память с данными об объекте WowObject. Что бы было понятнее приведу небольшой пример:
public class WowObject
{
private IntPtr BaseAddress;
private WowObjStruct ObjectData;
public WowObject(IntPtr address)
{
BaseAddress = address;
ObjectData = Memory.Process.Read<WowObjStruct>(BaseAddress);
}
public bool IsValid { get { return BaseAddress != IntPtr.Zero; } }
public T GetValue<T>(Enum index) where T : struct
{
return Memory.Process.Read<T>(ObjectData.Descriptors + (int)index * IntPtr.Size);
}
public void SetValue<T>(Enum index, T val) where T : struct
{
Memory.Process.Write<T>(ObjectData.Descriptors + (int)index * IntPtr.Size, val);
}
public bool IsA(WoWObjectTypeFlags flags)
{
return (GetValue<int>(Descriptors.ObjectFields.Type) & (int)flags) != 0;
}
}
Например мы хотим получить EntryId (EntryId — это что-то вроде класса, для объектов, т.е. 2 одинаковых предмета в игровом мире имеют одинаковый EntryId, но Guid у них разный), вот базовые дескрипторы для WowObject:
public enum ObjectFields
{
Guid = 0,
Data = 2,
Type = 4,
EntryId = 5,
DynamicFlags = 6,
Scale = 7,
End = 8,
}
[Flags]
public enum ObjectDynamicFlags : uint
{
Invisible = 1 << 0,
Lootable = 1 << 1,
TrackUnit = 1 << 2,
TaggedByOther = 1 << 3,
TaggedByMe = 1 << 4,
Unknown = 1 << 5,
Dead = 1 << 6,
ReferAFriendLinked = 1 << 7,
IsTappedByAllThreatList = 1 << 8,
}
Очевидно, что код будет следующим:
public class WowObject
{
//Ранее объявленные члены класса
public int Entry
{
get { return GetValue<int>(ObjectFields.EntryId); }
}
}
Все игровые объекты игры хранятся последовательно, т.е. структура следующего объекта будет найдена по смещению 0x28 (см. WowObjStruct [6]) от текущего указателя при условии, что он существует. Теперь разберемся как найти все структуры игры. Всеми WowObject заправляет менеджер объектов (далее ObjectManager).
[StructLayout(LayoutKind.Sequential)]
struct TSExplicitList // 12
{
public TSList baseClass; // 12
}
[StructLayout(LayoutKind.Sequential)]
struct TSList // 12
{
public int m_linkoffset; // 4
public TSLink m_terminator; // 8
}
[StructLayout(LayoutKind.Sequential)]
struct TSLink // 8
{
public IntPtr m_prevlink; //TSLink *m_prevlink // 4
public IntPtr m_next; // C_OBJECTHASH *m_next // 4
}
[StructLayout(LayoutKind.Sequential)]
struct TSHashTable // 44
{
public IntPtr vtable; // 4
public TSExplicitList m_fulllist; // 12
public int m_fullnessIndicator; // 4
public TSGrowableArray m_slotlistarray; // 20
public int m_slotmask; // 4
}
[StructLayout(LayoutKind.Sequential)]
struct TSBaseArray // 16
{
public IntPtr vtable; // 4
public uint m_alloc; // 4
public uint m_count; // 4
public IntPtr m_data;//TSExplicitList* m_data; // 4
}
[StructLayout(LayoutKind.Sequential)]
struct TSFixedArray // 16
{
public TSBaseArray baseClass; // 16
}
[StructLayout(LayoutKind.Sequential)]
struct TSGrowableArray // 20
{
public TSFixedArray baseclass; // 16
public uint m_chunk; // 4
}
[StructLayout(LayoutKind.Sequential)]
struct CurMgr // 248 bytes x86, 456 bytes x64
{
public TSHashTable VisibleObjects; // m_objects 44
public TSHashTable LazyCleanupObjects; // m_lazyCleanupObjects 44
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
// m_lazyCleanupFifo, m_freeObjects, m_visibleObjects, m_reenabledObjects, whateverObjects...
public TSExplicitList[] Links; // Links[10] has all objects stored in VisibleObjects it seems 12 * 11 = 132
#if !X64
public int Unknown1; // wtf is that and why x86 only? // 4
public int Unknown2; // not sure if this actually reflects the new object manager structure, but it does get the rest of the struct aligned correctly
public int Unknown3; // not sure if this actually reflects the new object manager structure, but it does get the rest of the struct aligned correctly
#endif
public ulong ActivePlayer; // 8
public int PlayerType; // 4
public int MapId; // 4
public IntPtr ClientConnection; // 4
public IntPtr MovementGlobals; // 4
}
А это смещения для конкретной версии World of Warcraft
public enum ObjectManager
{
connection = 0xEC4140,
objectManager = 0x462c,
}
Сейчас объясню как их найти. Для начала запускаем IDA и открываем в нем, уже надеюсь скачанный, Wow.exe. Как откроется, запоминаем BaseAddress, чаще всего он 0x400000, жмем Ctrl+L и ищем метку aObjectmgrclien. После перехода на нее жмем Ctrl+X и открываем последний референс. Вы должны увидеть, что-то такое:
push 0
push 0A9Fh
push offset aObjectmgrclien ; "ObjectMgrClient.cpp"
push 100h
call sub_5DC588
test eax, eax
jz short loc_79EEEB
mov ecx, eax
call sub_79E1E1
jmp short loc_79EEED
loc_79EEEB:
xor eax, eax
loc_79EEED:
mov ecx, dword_12C4140
fldz
mov [ecx+462Ch], eax
Нас интересует первый dword, это dword_12C4140 и следующий за ним mov [ecx+462Ch], eax. Отсюда получаем, connection = 0x12C4140 — 0x400000 = 0xEC4140, objectManager = 0x462C. Для пересчета, я использую HackCalc [7]
public class ObjectManager : IEnumerable<WowObject>
{
private CurMgr _curMgr;
private IntPtr _baseAddress;
private WowGuid _activePlayer;
private WowPlayer _activePlayerObj;
public void UpdateBaseAddress()
{
var connection = Memory.Process.Read<IntPtr>((int)ObjectManager.connection, true);
_baseAddress = Memory.Process.Read<IntPtr>(connection + (int)ObjectManager.objectManager);
}
private IntPtr BaseAddress
{
get { return _baseAddress; }
}
public WowGuid ActivePlayer
{
get { return _activePlayer; }
}
public WowPlayer ActivePlayerObj
{
get { return _activePlayerObj; }
}
public IntPtr ClientConnection
{
get { return _curMgr.ClientConnection; }
}
public IntPtr FirstObject()
{
return _curMgr.VisibleObjects.m_fulllist.baseClass.m_terminator.m_next;
}
public IntPtr NextObject(IntPtr current)
{
return Memory.Process.Read<IntPtr>(current + _curMgr.VisibleObjects.m_fulllist.baseClass.m_linkoffset + IntPtr.Size);
}
public IEnumerable<WowObject> GetObjects()
{
_curMgr = Memory.Process.Read<CurMgr>(BaseAddress);
_activePlayer = new WowGuid(_curMgr.ActivePlayer);
IntPtr first = FirstObject();
while (((first.ToInt64() & 1) == 0) && first != IntPtr.Zero)
{
var wowObject = new WowObject(first);
if (wowObject.Guid.Value == _curMgr.ActivePlayer)
{
_activePlayerObj = new WowPlayer(first);
}
first = NextObject(first);
}
}
public IEnumerator<WowObject> GetEnumerator()
{
return GetObjects().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Таким образом мы можем получить все WowObject, ну и давайте научимся находить всех игровых юнитов (это все игровые и не игровые персонажи). Введем класс WowUnit:
public enum UnitField
{
CachedSubName = 0,
UnitClassificationOffset2 = 32,
CachedQuestItem1 = 48,
CachedTypeFlag = 76,
IsBossOffset2 = 76,
CachedModelId1 = 92,
CachedName = 108,
UNIT_SPEED = 128,
TaxiStatus = 0xc0,
TransportGUID = 2096,
UNIT_FIELD_X = 0x838,
UNIT_FIELD_Y = 0x83C,
UNIT_FIELD_Z = 0x840,
UNIT_FIELD_R = 2120,
DBCacheRow = 2484,
IsBossOffset1 = 2484,
UnitClassificationOffset1 = 2484,
CanInterrupt = 3172,
CastingSpellID = 3256,
ChannelSpellID = 3280,
}
public enum UnitFields
{
Charm = ObjectFields.End + 0,
Summon = 10,
Critter = 12,
CharmedBy = 14,
SummonedBy = 16,
CreatedBy = 18,
DemonCreator = 20,
Target = 22,
BattlePetCompanionGUID = 24,
ChannelObject = 26,
ChannelSpell = 28,
SummonedByHomeRealm = 29,
Sex = 30,
DisplayPower = 31,
OverrideDisplayPowerID = 32,
Health = 33,
Power = 34,
MaxHealth = 39,
MaxPower = 40,
PowerRegenFlatModifier = 45,
PowerRegenInterruptedFlatModifier = 50,
Level = 55,
EffectiveLevel = 56,
FactionTemplate = 57,
VirtualItemID = 58,
Flags = 61,
Flags2 = 62,
AuraState = 63,
AttackRoundBaseTime = 64,
RangedAttackRoundBaseTime = 66,
BoundingRadius = 67,
CombatReach = 68,
DisplayID = 69,
NativeDisplayID = 70,
MountDisplayID = 71,
MinDamage = 72,
MaxDamage = 73,
MinOffHandDamage = 74,
MaxOffHandDamage = 75,
AnimTier = 76,
PetNumber = 77,
PetNameTimestamp = 78,
PetExperience = 79,
PetNextLevelExperience = 80,
ModCastingSpeed = 81,
ModSpellHaste = 82,
ModHaste = 83,
ModRangedHaste = 84,
ModHasteRegen = 85,
CreatedBySpell = 86,
NpcFlag = 87,
EmoteState = 89,
Stats = 90,
StatPosBuff = 95,
StatNegBuff = 100,
Resistances = 105,
ResistanceBuffModsPositive = 112,
ResistanceBuffModsNegative = 119,
BaseMana = 126,
BaseHealth = 127,
ShapeshiftForm = 128,
AttackPower = 129,
AttackPowerModPos = 130,
AttackPowerModNeg = 131,
AttackPowerMultiplier = 132,
RangedAttackPower = 133,
RangedAttackPowerModPos = 134,
RangedAttackPowerModNeg = 135,
RangedAttackPowerMultiplier = 136,
MinRangedDamage = 137,
MaxRangedDamage = 138,
PowerCostModifier = 139,
PowerCostMultiplier = 146,
MaxHealthModifier = 153,
HoverHeight = 154,
MinItemLevel = 155,
MaxItemLevel = 156,
WildBattlePetLevel = 157,
BattlePetCompanionNameTimestamp = 158,
InteractSpellID = 159,
End = 160,
}
public class WowUnit : WowObject
{
public WowUnit(IntPtr address)
: base(address)
{
}
public int Health
{
get { return GetValue<int>(UnitFields.Health); }
}
public int MaxHealth
{
get { return GetValue<int>(UnitFields.MaxHealth); }
}
public bool IsAlive
{
get { return !IsDead; }
}
public bool IsDead
{
get { return this.Health <= 0 || (DynamicFlags & ObjectDynamicFlags.Dead) != 0; }
}
public ulong TransportGuid
{
get { return GetValue<ulong>(UnitField.TransportGUID); }
}
public bool InTransport
{
get { return TransportGuid > 0; }
}
public Vector3 Position
{
get
{
if (Pointer == IntPtr.Zero) return Vector3.Zero;
if (InTransport)
{
var wowObject = Memory.ObjectManager.GetObjectByGUID(TransportGuid);
if (wowObject != null)
{
var wowUnit = new WowUnit(wowObject.Pointer);
if (wowUnit.IsValid && wowUnit.IsAlive)
return wowUnit.Position;
}
}
var position = new Vector3(
Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_X),
Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_Y),
Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_Z),
"None");
return position;
}
}
}
Остается проитерировать ObjectManager и проверить wowObject.IsA(WoWObjectTypeFlags.Unit). На сегодня все, статья и так вышла огромная для усвоения. Если что-то будет не получаться, пишите в личку с ссылочкой на сорсы на GitHub. Желаю удачи!
Автор: 3axap4eHko
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/84111
Ссылки в тексте:
[1] Часть 0 — Поиск точки внедрения кода: http://habrahabr.ru/post/251137/
[2] Часть 1 — Внедрение и исполнение стороннего кода: http://habrahabr.ru/post/251149/
[3] Часть 2 — Прячем код от посторонних глаз: http://habrahabr.ru/post/251199/
[4] Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры): http://habrahabr.ru/post/251353/
[5] Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение): http://habrahabr.ru/post/251479/
[6] WowObjStruct: #WowObjStruct
[7] HackCalc: http://fbe.am/vNt
Нажмите здесь для печати.