Bloog Bot

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

A Brief Detour

The next thing we should do is give our bot the ability to move around. The most common technique for making bots move hooks into the Click-To-Move function that's built into the vanilla WoW client. I didn't even know Click-To-Move existed until I started playing around with botting way later on. Essentially CTM lets you move your character by right-clicking a spot on the ground. We're also going to implement more traditional movement (WASD style) down the road, but CTM is what we'll use 95% of the time, so let's do that first.

CTM in the early versions of the WoW client has some strange issues related to framerate. If your framerate is higher than ~80 FPS, CTM doesn't work (see this thread at OwnedCore for more info). If your monitor is set to 60hz refresh rate, and you enable vSync, you won't run into any problems. I'm working with a monitor that has a 144hz refresh rate, and I'd prefer not to lower my refresh rate. Thankfully there's another solution. What we're going to do is artificially throttle the framerate of the WoW client by hooking the DirectX EndScene function. There are definitely easier solutions to this problem, and this may seem heavy-handed, but when we start talking about Warden down the road, function detouring is a very important tool to have in our toolbox, so I thought it made sense to introduce it here. This solution also has performance implications, but modern computers should have no problem running the vanilla WoW client, so I'm not too concerned about this.

So what is hooking? The wikipedia article has some good information with examples, but the here's the gist. The WoW client's rendering engine uses DirectX. If you recall the earlier chapter on game loops, you'll remember that there are typically 3 things that happen every frame:

  1. Handle Input
  2. Update Simulation
  3. Render Scene

Step 3 is relevant to this discussion. We won't get into the nitty gritty of how DirectX renders a frame, but what's important is that DirectX has a function called EndScene that is called once per frame. So back to the previous question - "what is hooking"? The WoW client calls EndScene internally, so whenever WoW tries to call EndScene we're going to trick it into calling some other function that we've written. Here's the high level:

  1. Write our own EndSceneHook method that does some extra stuff, then calls the actual EndScene function
  2. Find the memory location of the EndScene function in the WoW process (there are actually multiple steps involved here)
  3. Write to the WoW process memory, replacing the address of EndScene with EndSceneHook

Step #2 is a bit tricky. The EndScene function is not always in the same memory location, but we do have a reliable way of finding it. There's a ISceneEnd function in the WoW client that calls EndScene internally, and thankfully, ISceneEnd is at a static memory address. So doing some familiar memory reading and pointer dereferencing, we can reliably find the address of EndScene at runtime. However the way we do this is a bit different than we've done it previously. When ISceneEnd is called internally by the WoW client's rendering engine, a pointer is passed as a parameter. The memory address of the EndScene function is at an offset from this pointer parameter. So we actually need to hook the ISceneEnd function with our own method that first sets a private variable as a pointer to the EndScene function. So let's modify our list of steps above to be a bit more specific:

  1. Find the memory location of the EndScene function:
    • Write custom ISceneEndHook method that will find the memory address of the EndScene function and set a private variable to that address, then unhook itself
    • Hook ISceneEnd function to detour to ISceneEndHook
  2. Write custom EndSceneHook method that will make sure frames don't render faster than ~16ms (~60fps)
  3. Hook EndScene function to detour to EndSceneHook using the pointer to EndScene that we found in step 1

I'm about to show a lot of new code. If this is confusing, I encourage you to spend some time studying the code, dissecting it and taking each chunk one line at a time. Drawing the flow of execution out on a piece of paper always helps me.

First - now that we're writing to memory, we need to give MemoryManager a new WriteBytes method. This new method uses WriteProcessMemory from kernel32.dll which we used before, so that should look familiar. Here's what MemoryManager looks like now:

public static unsafe class MemoryManager
{
    [DllImport("kernel32.dll")]
    internal static extern bool WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        byte[] lpBuffer,
        int dwSize,
        ref int lpNumberOfBytesWritten);

    [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 ulong ReadUlong(IntPtr address)
    {
        try
        {
            return *(ulong*)address;
        }
        catch (AccessViolationException ex)
        {
            Console.WriteLine("Access Violation on " + address + " with type Ulong");
            return default;
        }
    }

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

    [HandleProcessCorruptedStateExceptions]
    internal static float ReadFloat(IntPtr address)
    {
        try
        {
            return *(float*)address;
        }
        catch (AccessViolationException ex)
        {
            Console.WriteLine("Access Violation on " + address + " with type Float");
            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;
        }
    }

    internal void WriteBytes(IntPtr address, byte[] bytes)
    {
        var process = Process.GetProcessesByName("WoW")[0].Handle;
        int ret = 0;
        WriteProcessMemory(process, address, bytes, bytes.Length, ref ret);
    }
}

And here's our new DirectXManager class that encapsulates all the hooking and throttling code (comments should help explain the high level):

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace BloogBot
{
    public static class DirectXManager
    {
        const int I_SCENE_END_FUN_PTR = 0x005A17A0;

        static int lastFrameTick;
        static int timeBetweenFrame;
        static int waitTilNextFrame;

        // endSceneDetour needs to be a class field (as opposed to local variable)
        // otherwise the GC will clean it up and crash the bot
        static Direct3D9EndScene endSceneDetour;
        static Direct3D9EndScene endSceneOriginal;
        static IntPtr endScenePtr;
        static Direct3D9ISceneEnd iSceneEndDelegate;
        static IntPtr target;
        static List<byte> original;

        // if frames are rendering faster than once every ~16ms (60fps), slow them down
        // this corrects an issue where ClickToMove doesn't work when your monitor has a refresh rate above ~80
        internal static void ThrottleFPS()
        {
            GetEndScenePtr();
            endSceneOriginal = Marshal.GetDelegateForFunctionPointer<Direct3D9EndScene>(MemoryManager.ReadIntPtr(endScenePtr));
            endSceneDetour = new Direct3D9EndScene(EndSceneHook);

            var addrToDetour = Marshal.GetFunctionPointerForDelegate(endSceneDetour);
            var customBytes = BitConverter.GetBytes((int)addrToDetour);
            MemoryManager.WriteBytes(endScenePtr, customBytes);
        }

        static int EndSceneHook(IntPtr device)
        {
            if (lastFrameTick != 0)
            {
                timeBetweenFrame = Environment.TickCount - lastFrameTick;
                if (timeBetweenFrame < 15)
                {
                    var newCount = Environment.TickCount;
                    waitTilNextFrame = 15 - timeBetweenFrame;
                    newCount += waitTilNextFrame;
                    while (Environment.TickCount < newCount) { }
                }
            }
            lastFrameTick = Environment.TickCount;

            return endSceneOriginal(device);
        }

        static void GetEndScenePtr()
        {
            iSceneEndDelegate = Marshal.GetDelegateForFunctionPointer<Direct3D9ISceneEnd>((IntPtr)I_SCENE_END_FUN_PTR);
            target = Marshal.GetFunctionPointerForDelegate(iSceneEndDelegate);
            var hook = Marshal.GetFunctionPointerForDelegate(new Direct3D9ISceneEnd(ISceneEndHook));

            // note the original bytes so we can unhook ISceneEnd after finding endScenePtr
            original = new List<byte>();
            original.AddRange(MemoryManager.ReadBytes(target, 6));

            // hook ISceneEnd
            var detour = new List<byte> { 0x68 }; // opcode for push instruction
            var bytes = BitConverter.GetBytes(hook.ToInt32());
            detour.AddRange(bytes);
            detour.Add(0xC3); // opcode for retn instruction
            MemoryManager.WriteBytes(target, detour.ToArray());

            // wait for ISceneEndHook to set endScenePtr
            while (endScenePtr == default(IntPtr))
                Task.Delay(3);
        }

        static IntPtr ISceneEndHook(IntPtr ptr)
        {
            var ptr1 = MemoryManager.ReadIntPtr(IntPtr.Add(ptr, 0x38A8));
            var ptr2 = MemoryManager.ReadIntPtr(ptr1);
            endScenePtr = IntPtr.Add(ptr2, 0xa8);

            // unhook ISceneEnd
            MemoryManager.WriteBytes(target, original.ToArray());

            return iSceneEndDelegate(ptr);
        }

        delegate int Direct3D9EndScene(IntPtr device);

        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
        delegate IntPtr Direct3D9ISceneEnd(IntPtr unk);
    }
}

This is pretty hairy, but the consolation is that we won't really have to touch this class anymore. Let's take a look at some of the more interesting bits.

First take a look at the last two lines of the GetEndScenePtr method. Because ISceneEndHook is actually responsible for setting the endScenePtr variable, and ISceneEndHook is actually called internally by the WoW client's rendering engine once per frame, we actually need to wait for that variable to get set before letting GetEndScenePtr return. If not, line 40 in the ThrottleFPS method will execute and endScenePtr will be equal to IntPtr.Zero and it'll blow up.

Also notice the comment on lines 20-21. Knowing how the garbage collector works, if we were to make endSceneDetour a local variable on line 41, as soon as the ThrottleFPS method returned, that local variable would go out of scope and would be cleaned up the next time the garbage collector ran. We've detoured the WoW client to that endSceneDetour function, and if it's cleaned up by the garbage collector, its memory space would be returned to the operating system, and the next time that function was called we'd get a read/write error and the bot would crash. Making it a class field will prevent this from happening. As a side note - when we initialize the DirectXManager below in our MainViewModel class, the DirectXManager object goes out of scope as soon as the constructor returns, so I would assume we'd still run into issues with the garbage collector cleaning up the endSceneDetour delegate, but that doesn't seem to be the case. I'm not quite sure why - perhaps it's a quirk of how the garbage collector interacts with WPF UI code. I'll have to investigate that further.

Some of you may be wondering why the techniques we use to hook the two functions look so different. When we hook ISceneEnd we write 6 bytes to memory (0x68, the 4 bytes that represent the memory address of the ISceneEndHook method, then 0xC3). But when we hook EndScene we just write the 4 byte memory address of EndSceneHook. Why?

In the first case, we're using what's called the Push/Retn method (see Method II: Push/Return here for more info). 0x68 is the push instruction in assembly. It should be followed by a 4 byte memory address. 0xC3 is the retn instruction in assembly. The push instruction pushes the 4 byte memory address onto the stack, then the retn instruction pops a 4 byte memory address off the stack and into the Instruction Pointer which will start execution of the function found at the memory address that was provided between the push and retn instructions. If you look at the disassembled WoW client, this is what the instructions look like, so we must follow suit and write our hook this way.

In the case of the EndScene function hook - this function is actually stored in the DirectX virtual function table. We simply have to replace the memory address of the EndScene function stored in that table with EndSceneHook and we're in business. DirectX vtable hooking is actually a pretty common thing to do, so a Google search will yield plenty of good information if you want to learn more. Programs like Fraps hook DirectX for example.

This was pretty hard for me to wrap my head around at first, so if you find this tricky, it's totally fine to copy/paste the code for now and circle back to study it later. It's also possible to totally ignore this chapter if your machine doesn't suffer from the ClickToMove issue. You can also just turn the refresh rate on your monitor down.

Hooking will come up again when we talk about circumventing Warden. As an example, the WoW client has a function that scans your process's memory for any modified bytes. Certain hacks require you to modify the memory of the WoW process, but this is detectable by Warden. You can hook that memory scan function to first revert any modified bytes to their original values, then let the memory scan happen, then change the bytes back to their "hacked" values. Done successfully, Warden will send a clean report back to the server.

The last thing to do is add our new DirectXManager code. You can add it to the OnStartup method in App.xaml.cs which runs when the WPF application starts up:

using System;
using System.Diagnostics;
using System.Windows;

namespace BloogBot
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            Debugger.Launch();

            // throttle framerate to fix ClickToMove on higher refresh rate monitors
            DirectXManager.ThrottleFPS();

            var mainWindow = new MainWindow();
            Current.MainWindow = mainWindow;
            mainWindow.Closed += (sender, args) => { Environment.Exit(0); };
            mainWindow.Show();

            base.OnStartup(e);
        }
    }
}

That takes care of one issue. If you tried using ClickToMove before before hooking EndScene you'd have noticed that the player looks like he can't turn more than 5 degrees a second. After this fix, you should be able to issue a ClickToMove command anywhere around the player, and he'll move in that direction. But if you play around with ClickToMove you may notice that the player will frequently stop prematurely. This is another issue related to ClickToMove in early versions of the WoW client.

The fix for this second issue is quite easy to implement, but unfortunately I don't have an explanation for how or why it works. You can read a bit about it on this thread at OwnedCore, but there isn't an explanation of why it works. If you look at the decompiled WoW client, you'll see this memory location isn't actually accessed by any functions, so I have no idea what purpose it could serve.

All we have to do is write 0 to 4 bytes starting at memory address 0x860A90. Weird, huh? If anybody has a better explanation for this please let me know. Add this code to OnStartup right after the DirectXManager.ThrottleFPS():

// enable ClickToMove fix
memoryManager.WriteBytes((IntPtr)CLICK_TO_MOVE_FIX, new byte[] { 0, 0, 0, 0 });

With both fixes in place, ClickToMove should work flawlessly. It's a pain in the ass to get it working, but our bot relies heavily on ClickToMove so it's worth the effort.

With all that out of the way, we can finally talk about implementing ClickToMove. While it's possible to manually initiate click to move by writing to 4 memory locations: CtmX, CtmY, CtmZ, and CtmType, the easiest way to implement ClickToMove is by calling the ClickToMove function in the WoW client. First create a new ClickType enum:

enum ClickType
{
    // we'll add more to this later
    Move = 0x4
}

Then create a new XYZ enum:

[StructLayout(LayoutKind.Sequential)]
public struct XYZ
{
    internal float X;
    internal float Y;
    internal float Z;

    internal XYZ(float x, float y, float z)
    {
        X = x;
        Y = y;
        Z = z;
    }
}

And finally, add the following code to our Functions class:

const int CLICK_TO_MOVE_FUN_PTR = 0x00611130;

[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
delegate void ClickToMoveDelegate(
    IntPtr playerPtr, 
    ClickType clickType, 
    ref ulong interactGuidPtr, 
    ref XYZ positionPtr, 
    float precision);

static readonly ClickToMoveDelegate ClickToMoveFunction = 
    Marshal.GetDelegateForFunctionPointer((IntPtr)CLICK_TO_MOVE_FUN_PTR);

internal static void ClickToMove(IntPtr playerPtr, ClickType clickType, Position position)
{
    ulong interactGuidPtr = 0;
    var xyz = new XYZ(position);
    ClickToMoveFunction(playerPtr, clickType, ref interactGuidPtr, ref xyz, 2);
}

Most of this should look familiar by now. The internal ClickToMove function is at static memory address 0x00611130 so we register a delegate and wire things up. The only difference is that we're referencing the new XYZ struct here. The WoW client's ClickToMove function requires this - so we'll simply convert our Position object to an XYZ object before calling the internal function. This function has 5 parameters:

  1. playerPtr - pointer to the local player object.
  2. clickType - there are a bunch of different clickTypes. I'm not sure what they all do. We only care about Move (4) for now.
  3. interactGuidPtr - not sure what this is. Pass a 0 by ref.
  4. xyzPtr - desired destination. The WoW client expects an object that has 3 fields of type int that represent x, y and z coordinates. The fields should be laid out sequentially in memory, hence the StructLayout attribute. Pass it by ref.
  5. precision - not sure what this is. Pass 2.

Then add a new ClickToMove function to WoWPlayer:

internal void ClickToMove(Position position) => Functions.ClickToMove(Pointer, ClickType.Move, position);

Now let's modify our Test method to find the nearest target and issue a ClickToMove command to it's location:

void Test()
{
    ObjectManager.EnumerateVisibleObjects();

    var player = ObjectManager.Player;

    var units = ObjectManager.Units;
    var closestUnit = units.OrderBy(u => u.Position.DistanceTo(player.Position)).First();

    Log($"Closest unit to player: {closestUnit.Name}");
    Log($"Position: {closestUnit.Position}");
    Log($"Distance: {closestUnit.Position.DistanceTo(player.Position)}");

    player.ClickToMove(closestUnit.Position);
}

Fire the client up and give it a test drive. You should see your character moving to the nearest unit:

We've covered a lot of territory in this section and laid the groundwork for a lot of cool stuff coming up later. In the next chapter we're going to start talking about giving our bot a behavior loop using a state machine.

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