Bloog Bot

Chapter 8
Drew Kestell, 2024
drew.kestell@gmail.com

Sentience

At the end of Chapter 7, we created the implementation for the EnumerateVisibleObjects function. We successfully called the function from the WoW client and saw a bunch of objects around the player. Our code can currently differentiate between different types of objects using the ObjectType property on the WoWObject class, but that's not entirely useful by itself. What we really want is to bring our different object types to life by giving them distinct properties and methods. We'll do that by creating a class hierarchy using WoWObject as the base.

Each object type defined on our ObjectType enum represents a distinct type of object in the game with its own set of properties. For example - it makes sense for a Player or Unit to have Health, but not necessarily an Item. But all objects have a guid that's used to uniquely identify that object, which is why Guid is on the base class (WoWObject). I mentioned above the object types are represented using a class hierarchy. Because each object type is its own class, these classes each have their own distinct properties to represent the state and behavior of the object types. The properties on the different object types have been discovered through reverse engineering the WoW client, but like usual we're going to build on the work of others and look them up. Take a look at this page to see a pretty thorough breakdown of the different object types.

We aren't going to implement every object type right now. One of the primary goals of a bot is to find and kill enemies, so Unit is probably a good place to start. Create a class WoWUnit that inherits from WoWObject, and give the new class a constructor that calls the constructor on the base class to satisfy the compiler:

class WoWUnit : WoWObject
{
    internal WoWUnit(IntPtr pointer, ulong guid, ObjectType objectType)
        : base(pointer, guid, objectType)
    {
    }
}

Right now the WoWUnit class is exactly the same as WoWObject, so let's change that. First, we have to understand an implementation detail in the WoW client. Units have their details stored in a separate memory location. These details are known as "Descriptors". For each unit, the memory location of its descriptor collection is stored at a 0x8 byte offset from the base address of the unit. So there are two steps to finding the health of a unit. First, we have to find the memory address of its descriptor collection by reading a pointer from memory at BaseAddress + 0x8, then we read an int from memory using DescriptorCollectionAddress + HealthOffset. To find the HealthOffset, check out the wowdev wiki I linked above. Under the Unit Fields section you'll see the line: UNIT_FIELD_HEALTH=0x58. This means that the Health property is located at an offset of 0x58 bytes from the DescriptorCollection address. This is a bit tricky, because we're searching memory in two steps. First we have to find the address of the unit's DescriptorCollection, then we add the HealthOffset to the memory location of the DescriptorCollection.

To assist with reading memory to get the unit's health, we're going to modify our MemoryManager class. Recall from a previous chapter that we created a MemoryManager class with a single method to read bytes from the WoW client's memory. We're going to add a new method to read ints. Code below:

public static unsafe class MemoryManager
{
    [HandleProcessCorruptedStateExceptions]
    internal static byte ReadByte(IntPtr address)
    {
        try
        {
            return *(byte*)address;
        }
        catch (AccessViolationException)
        {
            Console.WriteLine("Access Violation on " + address + " with type Byte");
            return default;
        }
    }

    [HandleProcessCorruptedStateExceptions]
    internal static int ReadInt(IntPtr address)
    {
        try
        {
            return *(int*)address;
        }
        catch (AccessViolationException)
        {
            Console.WriteLine("Access Violation on " + address + " with type Int");
            return default;
        }
    }
}

Now we'll add a Health property that uses MemoryManager to read the unit's health from memory. Putting it all together, it looks like this:

class WoWUnit : WoWObject
{
    // DESCRIPTORS
    const int DESCRIPTOR_OFFSET = 0x8;
    const int HEALTH_OFFSET = 0x58;

    internal WoWUnit(IntPtr pointer, ulong guid, ObjectType objectType)
        : base(pointer, guid, objectType)
    {
    }

    internal int Health
    {
        get
        {
            var ptr = MemoryManager.ReadInt(IntPtr.Add(Pointer, DESCRIPTOR_OFFSET));
            return MemoryManager.ReadInt(new IntPtr(ptr + HEALTH_OFFSET));
        }
    }
}

And because we will be adding more code that reads from the Descriptors structure later on, let's make our code a bit more reusable. First, let's add a new method to MemoryManager:

[HandleProcessCorruptedStateExceptions]
static internal IntPtr ReadIntPtr(IntPtr address)
{
    try
    {
        return *(IntPtr*)address;
    }
    catch (AccessViolationException ex)
    {
        Console.WriteLine("Access Violation on " + address + " with type IntPtr");
        return default;
    }
}

Then update WoWObject.cs to look like this:

public abstract class WoWObject
{
    const int DESCRIPTOR_OFFSET = 0x8;
    
    public readonly IntPtr Pointer;
    public readonly ulong Guid;

    internal readonly ObjectType ObjectType;

    internal WoWObject(IntPtr pointer, ulong guid, ObjectType objectType)
    {
        Guid = guid;
        Pointer = pointer;
        ObjectType = objectType;
    }

    protected IntPtr GetDescriptorPtr() =>
        MemoryManager.ReadIntPtr(IntPtr.Add(Pointer, DESCRIPTOR_OFFSET));
}

Then update WoWUnit to call the GetDescriptorPtr method on the base class to retrieve the Health value:

class WoWUnit : WoWObject
{
    // DESCRIPTORS
    const int HEALTH_OFFSET = 0x58;

    internal WoWUnit(IntPtr pointer, ulong guid, ObjectType objectType)
        : base(pointer, guid, objectType)
    {
    }

    internal int Health => MemoryManager.ReadInt(GetDescriptorPtr() + HEALTH_OFFSET);
}

Now we need to modify the Callback method on the ObjectManager class to actually use the WoWUnit class. We'll use a switch statement to switch on objectType to determine which type of object to instantiate when populating the object list:

static int Callback(int filter, ulong guid)
{
    var pointer = Functions.GetObjectPtr(guid);
    var objectType = (ObjectType)MemoryManager.ReadByte(
        IntPtr.Add(pointer, 
        OBJECT_TYPE_OFFSET)
    );

    switch (objectType)
    {
        case ObjectType.Unit:
            Objects.Add(new WoWUnit(pointer, guid, objectType));
            break;
        default:
            Objects.Add(new WoWObject(pointer, guid, objectType));
            break;
    }
    
    return 1;
}

The last thing to do is test our code to make sure everything is working correctly. As our object hierarchy gets more complicated, we're going to want the ability to only look at certain types of objects, so let's add a helper method to the ObjectManager class that will only return objects of type WowUnit:

internal static IEnumerable<WoWUnit> Units => Objects.OfType<WoWUnit>();

And let's update the Test method in the UI to call this new method and print the unit's health:

void Test()
{
    ObjectManager.EnumerateVisibleObjects();

    var units = ObjectManager.Units;
    var unitCount = units.Count();

    Log($"{unitCount} units found:\n");

    foreach (var unit in units)
    {
        Log($"Guid: {unit.Guid}");
        Log($"Pointer: {unit.Pointer}");
        Log($"Health: {unit.Health}");
        Log($"ObjectType: {unit.ObjectType.ToString()}\n");
    }  
}

Now if you log in and click Test, you should see all units around the player, including their health:

It would also be useful to see the name of a unit. As discussed above, a unit's health is stored as a descriptor in a separate memory location. Almost everything you'd want to know about a unit is stored as a descriptor, but names are an exception. To be honest, I'm not sure why, but it's not too hard to find. Searching through the ownedcore client dump thread, I discovered that the pointer to the unit's name is stored at an offset from the base address of the unit. So there are three steps we need to take here. As a side note - at this point I hope the basics of pointer arithmetic and memory reading are clear, so I'm going to explain less about the multiple steps taken to read memory, and instead just show the code (if this is still confusing, go back to the first few chapters and work through the CheatEngine examples until you understand exactly what's happening). Here's the WoWUnit class with the new Name property:

class WoWUnit : WoWObject
{
    // DESCRIPTORS
    const int HEALTH_OFFSET = 0x58;

    // OTHER
    const int NAME_OFFSET = 0xB30;

    internal WoWUnit(IntPtr pointer, ulong guid, ObjectType objectType)
        : base(pointer, guid, objectType)
    {
    }

    internal int Health => MemoryManager.ReadInt(GetDescriptorPtr() + HEALTH_OFFSET);

    internal string Name
    {
        get
        {
            var ptr1 = MemoryManager.ReadInt(IntPtr.Add(Pointer, NAME_OFFSET));
            var ptr2 = MemoryManager.ReadInt((IntPtr)ptr1);
            return MemoryManager.ReadString((IntPtr)ptr2);
        }
    }
}

You'll notice a new ReadString method on the MemoryManager class. We'll discuss this in more detail later on, but for now, you can use this code to read strings from the WoW process:

public static unsafe class MemoryManager
{
    [HandleProcessCorruptedStateExceptions]
    internal static byte ReadByte(IntPtr address)
    {
        try
        {
            return *(byte*)address;
        }
        catch (AccessViolationException ex)
        {
            Console.WriteLine("Access Violation on " + address + " with type Byte");
            return default;
        }
    }

    [HandleProcessCorruptedStateExceptions]
    internal static int ReadInt(IntPtr address)
    {
        try
        {
            return *(int*)address;
        }
        catch (AccessViolationException ex)
        {
            Console.WriteLine("Access Violation on " + address + " with type Int");
            return default;
        }
    }

    [HandleProcessCorruptedStateExceptions]
    internal static string ReadString(IntPtr address)
    {
        var buffer = ReadBytes(address, 512);
        if (buffer.Length == 0)
            return default;

        var ret = Encoding.ASCII.GetString(buffer);

        if (ret.IndexOf('\0') != -1)
            ret = ret.Remove(ret.IndexOf('\0'));

        return ret;
    }

    [HandleProcessCorruptedStateExceptions]
    internal static byte[] ReadBytes(IntPtr address, int count)
    {
        try
        {
            var ret = new byte[count];
            var ptr = (byte*)address;

            for (var i = 0; i < count; i++)
                ret[i] = ptr[i];

            return ret;
        }
        catch (AccessViolationException ex)
        {
            Console.WriteLine("Access Violation on " + address + " with type Byte[]");
            return default;
        }
    }
}

Modify the test method to print the name of each unit, and test again. You should see something like this:

In the next chapter we'll create a class for the player and learn about world position.

Back to Top
...
Subscribe to the RSS feed to keep up with new chapters as they're released