Bloog Bot
Opening Our Eyes
Earlier, we saw a simple example of how to call a function in the WoW client using the GetPlayerGuid
function. Now we're going to take a look at one of the most useful functions for our bot: EnumerateVisibleObjects
.
Remember when I mentioned the different calling conventions available in C/C++? Up to this point we haven't had to worry about it because our first function GetPlayerGuid
didn't take any parameters. The new EnumerableVisibleObjects
has both parameters and a return value so we're going to need to think about calling convention here.
Remember that the default calling convention from C# is Stdcall
and we can override that with the UnmanagedFunctionPointer
annotation. The EnumerateVisibleObjects
function uses Fastcall
, so we're going to need to deviate from C#'s default behavior. Unfortunately, C# doesn't support Fastcall
(check out the API docs on MSDN), so we're going to have to get creative.
The solution is to write a small library in C++ that exposes a method implemented with Stdcall
that wraps the call to EnumerateVisibleObjects
using Fastcall
. So from C# we'll be calling EnumerateVisibleObjects
indirectly through our Fastcall library. The library is a DLL project with the following code:
#include "stdafx.h" BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } extern "C" { void __declspec(dllexport) __stdcall EnumerateVisibleObjects(unsigned int callback, int filter, unsigned int parPtr) { typedef void __fastcall func(unsigned int callback, int filter); func* function = (func*)parPtr; function(callback, filter); } }
Make sure you set the output directory of the project to ..\Bot\
so that it ends up in the same place as the rest of our bot's dependencies.
The one thing worth noting here is the line extern "C"
. This post on stackoverflow has a good explanation, but the gist is: the C++ compiler performs "name mangling" to support function overloading, but that makes it difficult to identify the function by name when using DllImport
from C#. So extern "C"
disables that name mangling.
With that done, let's wire up the EnumerateVisibleObjects
method in the Functions
class.
public static class Functions { // GetPlayerGuid const int GET_PLAYER_GUID_FUN_PTR = 0x00468550; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate ulong GetPlayerGuidDelegate(); static readonly GetPlayerGuidDelegate GetPlayerGuidFunction = Marshal.GetDelegateForFunctionPointer((IntPtr)GET_PLAYER_GUID_FUN_PTR); internal static ulong GetPlayerGuid() => GetPlayerGuidFunction(); // EnumerateVisibleObjects const int ENUMERATE_VISIBLE_OBJECTS_FUN_PTR = 0x00468380; [DllImport("FastCall.dll", EntryPoint = "EnumerateVisibleObjects")] static extern void EnumerateVisibleObjects(IntPtr callback, int filter, IntPtr ptr); internal static void EnumerateVisibleObjects(IntPtr callback, int filter) => EnumerateVisibleObjects( callback, filter, (IntPtr)ENUMERATE_VISIBLE_OBJECTS_FUN_PTR ); }
Notice that unlike GetPlayerGuid
, instead of using Marshal.GetDelegateForFunctionPointer
, we're going through our Fastcall library, so we have to import that using DllImport
and specifying the entry point (this would be the failure point if we didn't use extern "C"
as mentioned above). Also note that we don't need to specify a calling convention here because the default is Stdcall
and that's what our Fastcall library uses to wrap the internal Fastcall
function.
So, how does the EnumerateVisibleObjects
function in the WoW client work? It takes two parameters: a callback function (IntPtr
), and a filter (int
). For each object that's in range of the Player, the WoW client will call your callback function passing in the GUID of the object and the filter value that was passed in. The filter argument has to be provided, but it's up to you to implement that in your callback function (or ignore it). You could, for example, only enumerate over the first n objects using that filter argument.
The guid of an object isn't that useful to us. What we really want is a pointer to the object in memory so we can read from offsets from that memory address to find interesting values associated with the object. Thankfully the WoW client exposes a function called GetObjectPtr
that takes a GUID as a single parameter and returns a pointer to that game object. Here's the code:
public static class Functions { // GetPlayerGuid ... // EnumerateVisibleObjects ... // GetObjectPtr const int GET_OBJECT_PTR_FUN_PTR = 0x00464870; delegate IntPtr GetObjectPtrDelegate(ulong guid); static readonly GetObjectPtrDelegate GetObjectPtrFunction = Marshal.GetDelegateForFunctionPointer((IntPtr)GET_OBJECT_PTR_FUN_PTR); internal static IntPtr GetObjectPtr(ulong guid) => GetObjectPtrFunction(guid); }
Notice the lack of [UnmanagedFunctionPointer(CallingConvention.StdCall)]
annotating the delegate. In this case, the GetObjectPtr
function is implemented using Stdcall
in the WoW client so we can defer to C#'s default behavior.
All objects in WoW have a "type". There are obvious differences between an Item and a Player. These different types of objects all have a place in an object inheritance hierarchy. We will explore that hierarchy fully down the road, but for now, let's outline what the different types are using an enum
, and define a base class WoWObject
that holds a few properties that all objects in the game share.
enum ObjectType : byte { None, Item, Container, Unit, Player, GameObject, DynamicObject, Corpse }
class WoWObject { internal readonly ulong Guid; internal readonly IntPtr Pointer; internal readonly ObjectType ObjectType; internal WoWObject(ulong guid, IntPtr pointer, ObjectType objectType) { Guid = guid; Pointer = pointer; ObjectType = objectType; } }
There are 8 different object types in the WoW client, so we define those all in the enum
. Every object in the game has a GUID, a Pointer to the object, and an ObjectType, so we add those as properties to the WoWObject
class. Eventually we'll inherit from this base class when we create classes for items, players, etc, and those derived classes will have their own specific properties and methods.
There's one more thing to talk about before showing the ObjectManager
. Remember that the we pass a callback function to the EnumerateVisibleObjects
function, then that function calls our callback passing in the object's GUID as a parameter which we then pass to GetObjectPtr
to get a pointer to the object in memory. From there, we'll use the same principles that allowed us to find the Player's health in BloogsQuest from the first few chapters to find the WoW object's values in memory near the object's starting address. The first thing we'll find is the object's type, and to do that we need to know the offset of the ObjectType property from the start of the object in memory.
We also need a slightly different approach for reading memory from that location. In previous examples we used ReadProcessMemory
, but now that we're in-process that isn't necessary. Instead we can simply dereference the pointer, but in order to do so we'll need to be operating in an unsafe
context. Here's an MSDN article that talks about working with pointers from C# if you want to learn more. In our case, we're going to create a new class called MemoryManager
that we'll use to do our pointer dereferencing, and we'll also need to turn on the unsafe code flag for our project. The class doesn't have much, but we'll build on it as we move forward:
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; } } }
The type of an object is stored as a byte
offset 0x14
from the object's starting address, so the MemoryReader
class has a single method that reads from an address and parses the value as a byte
.
There are a few interesting things to mention here. First, MemoryManager is static for the same reason that we made Functions static earlier - we only ever need one, and we don't want to have to manage passing around a reference everywhere. Second, MemoryManager is marked as unsafe
- this allows us to do direct pointer dereferencing from C#. And finally, notice the [HandleProcessCorruptedStateExceptions]
annotation. Manually manipulating pointers is inherently dangerous - trying to dereference a null pointer will cause your program to crash. And this isn't something that the .NET Framework is equipped to handle by default. So if for some reason we call ReadByte
and pass in a bad memory address (null pointer, or a memory address in protected memory for example) - our program will crash, even if we wrap this in a try/catch. But by using the [HandleProcessCorruptedStateExceptions]
annotation, the .NET Framework WILL be able to recover from bad pointer dereferences! Such problems will throw an AccessViolationException
, so that's what we catch here.
Now we're going to add the ObjectManager
class. This class is responsible for defining the callback function and passing that to EnumerateVisibleObjects
, then maintaining a list of all the game objects that were found. Here's the code for that class:
using BloogBot.Game.Enums; using BloogBot.Game.Objects; using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace BloogBot.Game { public static class ObjectManager { const int OBJECT_TYPE_OFFSET = 0x14; [UnmanagedFunctionPointer(CallingConvention.ThisCall)] delegate int EnumerateVisibleObjectsCallback(int filter, ulong guid); static EnumerateVisibleObjectsCallback callback; static IntPtr callbackPtr; internal static IListObjects = new List (); static ObjectManager() { callback = Callback; callbackPtr = Marshal.GetFunctionPointerForDelegate(callback); } public static bool IsLoggedIn => Functions.GetPlayerGuid() > 0; internal static void EnumerateVisibleObjects() { if (IsLoggedIn) { Objects.Clear(); Functions.EnumerateVisibleObjects(callbackPtr, 0); } } static int Callback(int filter, ulong guid) { var pointer = Functions.GetObjectPtr(guid); var objectType = (ObjectType)MemoryManager.ReadByte( IntPtr.Add(pointer, OBJECT_TYPE_OFFSET) ); Objects.Add(new WoWObject(pointer, guid, objectType)); return 1; } } }
Recall that we pass our callback to the EnumerateVisibleObjects
function by its pointer, so we have to define a delegate and then wire up the Callback
method with the delegate using Marshal.GetFunctionPointerForDelegate
(The Callback uses the ThisCall
calling convention). Then we expose an EnumerateVisibleObjects
method that first clears the object list, then calls EnumerateVisibleObjects
in the WoW client, passing it the callback pointer and a filter of 0 (we'll be ignoring the filter in our callback).
The callback is the interesting part. Like I mentioned earlier, WoW's EnumerateVisibleObjects
function will call our callback for every object visible to the Player, so it's up to us to specify what we want to do with that object. For now, we'll just be adding all the objects to a list. But first we need to get the object's pointer which we do by calling GetObjectPtr
. From there, we retrieve the ObjectType
using our memory reader, reading from a 0x14
offset from the object's pointer.
We're ready for a test. In our ViewModel, let's change the Test button to call EnumerateVisibleObjects
then print some results to the log. Here's the new code in the ViewModel:
public class MainViewModel : INotifyPropertyChanged { ... ICommand testCommand; void Test() { ObjectManager.EnumerateVisibleObjects(); var objectCount = ObjectManager.Objects.Count; Log($"{objectCount} objects found:\n"); for (var i = 0; i < objectCount; i++) { Log($"Object {i + 1}"); Log($"Guid: {ObjectManager.Objects[i].Guid}"); Log($"Pointer: {ObjectManager.Objects[i].Pointer}"); Log($"ObjectType: {ObjectManager.Objects[i].ObjectType.ToString()}\n"); } } public ICommand TestCommand => testCommand ?? (testCommand = new CommandHandler(Test, true)); ... }
Let's log in and give it a spin:
We found 65 objects! The first few are of type Item
, but if you scroll down in that list you'll see others.
We've added quite a bit of new code to our solution in this section, so here's an overview of how I have things organized:
In the next chapter we'll create some derived classes that inherit from the base WoWObject
class and beef up the ObjectManager
class to let us find what we're looking for more easily.