
We are writing a bot for MMORPG with assembler and draenei. Part 3
- Tutorial

Disclaimer: The author is not responsible for your use of the knowledge gained in this article or damage resulting from their use. All information presented here is for educational purposes only. Especially for companies developing MMORPG to help them deal with bots. And, of course, the author of the article is not a bot driver, not a cheater, and he never was.
For those who have missed past articles, here is the content, and who have read everything, go ahead:
Content
- Part 0 - Finding a code injection point
- Part 1 - Implementation and execution of third-party code
- Part 2 - Hide the code from prying eyes
- Part 3 - Under the Sight of World of Warcraft 5.4.x (Structures)
- Part 4 - Under the Sight of World of Warcraft 5.4.x (Moving)
- Part 5 - Under the Sight of World of Warcraft 5.4.x (Custom Fireball)
Before you start, you can download World of Warcraft 5.4.x for download, for starters, we need only Wow.exe, so you can only download it. And now I’ll immerse you in the holy of holies of all cheaters and botovodov game World of Warcraft.
I'll start from far away. Each program has an algorithm by which it works and a memory in which it stores data, in other words - there is a code, and sometimes there is data, the data is in the code itself. So in order to have an idea of the world around us in the game, we need exactly the data. To get them, consider how they are stored in memory and what they are. So, we introduce the concept of a game object (hereinafter WowObject) - this is a basic object that is stored in memory and has the following Descriptors, ObjectType and Guid properties, here is its structure:
[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
}
where Guid is the unique value of the object in the game, OjectType is the type of the object in the game and can take the following values:
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
}
and Descriptors is a pointer to a memory with data about the WowObject. To make it clearer, I will give a small example:
public class WowObject
{
private IntPtr BaseAddress;
private WowObjStruct ObjectData;
public WowObject(IntPtr address)
{
BaseAddress = address;
ObjectData = Memory.Process.Read(BaseAddress);
}
public bool IsValid { get { return BaseAddress != IntPtr.Zero; } }
public T GetValue(Enum index) where T : struct
{
return Memory.Process.Read(ObjectData.Descriptors + (int)index * IntPtr.Size);
}
public void SetValue(Enum index, T val) where T : struct
{
Memory.Process.Write(ObjectData.Descriptors + (int)index * IntPtr.Size, val);
}
public bool IsA(WoWObjectTypeFlags flags)
{
return (GetValue(Descriptors.ObjectFields.Type) & (int)flags) != 0;
}
}
For example, we want to get EntryId (EntryId is something like a class, for objects, i.e. 2 identical objects in the game world have the same EntryId, but they have different Guid), here are the basic descriptors for 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,
}
Obviously, the code will be as follows:
public class WowObject
{
//Ранее объявленные члены класса
public int Entry
{
get { return GetValue(ObjectFields.EntryId); }
}
}
All game objects of the game are stored sequentially, i.e. the structure of the next object will be found at offset 0x28 (see WowObjStruct ) from the current pointer, provided that it exists. Now let's figure out how to find all the structure of the game. All WowObject runs the object manager (hereinafter ObjectManager).
Declare it using the following structures
[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
}
And these are offsets for a specific version of World of Warcraft
public enum ObjectManager
{
connection = 0xEC4140,
objectManager = 0x462c,
}
Now I will explain how to find them. To get started, run the IDA and open in it, already hopefully downloaded, Wow.exe. As it opens, we remember BaseAddress, most often it is 0x400000, press Ctrl + L and look for the label aObjectmgrclien . After switching to it, press Ctrl + X and open the last reference. You should see something like this:
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
We are interested in the first dword, this is dword_12C4140 and the following mov [ecx + 462Ch], eax . From here we get, connection = 0x12C4140 - 0x400000 = 0xEC4140, objectManager = 0x462C. For recount, I use HackCalc

public class ObjectManager : IEnumerable
{
private CurMgr _curMgr;
private IntPtr _baseAddress;
private WowGuid _activePlayer;
private WowPlayer _activePlayerObj;
public void UpdateBaseAddress()
{
var connection = Memory.Process.Read((int)ObjectManager.connection, true);
_baseAddress = Memory.Process.Read(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(current + _curMgr.VisibleObjects.m_fulllist.baseClass.m_linkoffset + IntPtr.Size);
}
public IEnumerable GetObjects()
{
_curMgr = Memory.Process.Read(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 GetEnumerator()
{
return GetObjects().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Thus, we can get all WowObject, well, let's learn how to find all game units (these are all game and non-game characters). We introduce the WowUnit class:
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(UnitFields.Health); }
}
public int MaxHealth
{
get { return GetValue(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(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(Pointer + (int)UnitField.UNIT_FIELD_X),
Memory.Process.Read(Pointer + (int)UnitField.UNIT_FIELD_Y),
Memory.Process.Read(Pointer + (int)UnitField.UNIT_FIELD_Z),
"None");
return position;
}
}
}
It remains to iterate the ObjectManager and check wowObject.IsA (WoWObjectTypeFlags.Unit). That's all for today, the article has already come out huge for assimilation. If something fails, write in a personal link with a link to the sorts on GitHub. Good luck!