Bloog Bot
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:
- Handle Input
- Update Simulation
- 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:
- Write our own
EndSceneHook
method that does some extra stuff, then calls the actualEndScene
function - Find the memory location of the
EndScene
function in the WoW process (there are actually multiple steps involved here) - Write to the WoW process memory, replacing the address of
EndScene
withEndSceneHook
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:
- Find the memory location of the
EndScene
function: - Write custom
ISceneEndHook
method that will find the memory address of theEndScene
function and set a private variable to that address, then unhook itself - Hook
ISceneEnd
function to detour toISceneEndHook
- Write custom
EndSceneHook
method that will make sure frames don't render faster than ~16ms (~60fps) - Hook
EndScene
function to detour toEndSceneHook
using the pointer toEndScene
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:
playerPtr
- pointer to the local player object.clickType
- there are a bunch of different clickTypes. I'm not sure what they all do. We only care aboutMove (4)
for now.interactGuidPtr
- not sure what this is. Pass a 0 by ref.xyzPtr
- desired destination. The WoW client expects an object that has 3 fields of typeint
that represent x, y and z coordinates. The fields should be laid out sequentially in memory, hence theStructLayout
attribute. Pass it by ref.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.