World of Warcraft Morph Hack
Today you’re going to learn about how a WoW morph hack works!
Posting this for two reasons:
- People cry when things don’t get updated and I’m sick of getting PMs.
- On the other hand, they cry anyway so rather than spoonfeed people I’ll give them the means to write their own hack.
Quick side note: Sorry if this post is hard to follow or understand, I’m kinda rushed at the moment. Please leave a comment and let me know if anything doesn’t make sense.
The concept is fairly simple. Every (unit) model has a unique display id. This id (from memory) is stored in WoWs DBC files (which are basically glorified csv files to hold localization info and other data that shouldn’t be hardcoded into the client). When a unit is created, its display id is stored in UNIT_FIELD_DISPLAYID and UNIT_FIELD_NATIVEDISPLAYID. Whenever a unit is ‘morphed’, for example by a spell like Polymorph or Hex, the UNIT_FIELD_DISPLAYID is overwritten with the new display id and the model is redrawn.
Your goal is to replicate the spell morph effect by forcing a new value for the display id and then calling the function to redraw the model. Obviously to do this you will need to find that function. At first this may seem like a difficult task but its actually quite simple.
Note: I’m assuming you already know how to enumerate objects and also get access to an object’s pStorage/descriptor-array/etc (names vary) in order for you to give UNIT_FIELD_DISPLAYID a new value. If you don’t, please consult the WoW Memory Editing forums on MMOwned, there is more than enough information there on the topic.
First you need to work out your starting point. Because morphing is (normally) initiated by the server there would most likely be a packet to perform the morph. If you dig into Mangos/ArcEmu/etc (or if you have a debug client you could use that instead) and view the opcode list you will see that you are in luck and that the packet SMSG_FORCE_DISPLAY_UPDATE (opcode 0×403) is probably what you want. You can find the packet handler for this by opening WoW in IDA and searching for ‘push 403h’ (which is the opcode being passed to the function which registers the packet handlers).
You will likely get two results for that search.
One like this:
push 403h
call sub_12345678
And another like this:
push <reg>
push offset sub_87654321
push 403h
call sub_23456789
The second is the one you are interested in because you can clearly see it is the one that registers the callback (it takes a function pointer and the opcode), the other one can be discarded (but if you look into it closely you will see it is resetting the opcode in question).
If you follow the function pointer you will be presented with something like the following (the code below is from 3.0.3 - I used an older client so you can’t ‘cheat’):
.text:0067EB00 sub_67EB00 proc near ; DATA XREF: sub_67F8F0+6E9o
.text:0067EB00
.text:0067EB00 var_8 = dword ptr -8
.text:0067EB00 var_4 = dword ptr -4
.text:0067EB00 arg_C = dword ptr 14h
.text:0067EB00
.text:0067EB00 push ebp
.text:0067EB01 mov ebp, esp
.text:0067EB03 mov ecx, [ebp+arg_C]
.text:0067EB06 sub esp, 8
.text:0067EB09 lea eax, [ebp+var_8]
.text:0067EB0C push eax
.text:0067EB0D push ecx
.text:0067EB0E call sub_69FE30
.text:0067EB13 mov edx, [ebp+var_4]
.text:0067EB16 mov eax, [ebp+var_8]
.text:0067EB19 push 65Fh
.text:0067EB1E push offset a_Unit_c_cpp ; “.\\Unit_C.cpp”
.text:0067EB23 push 8
.text:0067EB25 push edx
.text:0067EB26 push eax
.text:0067EB27 call sub_46D3C0
.text:0067EB2C add esp, 1Ch
.text:0067EB2F test eax, eax
.text:0067EB31 jz short loc_67EB3E
.text:0067EB33 push 1
.text:0067EB35 push 1
.text:0067EB37 mov ecx, eax
.text:0067EB39 call sub_67B2E0
.text:0067EB3E
.text:0067EB3E loc_67EB3E: ; CODE XREF: sub_67EB00+31j
.text:0067EB3E mov eax, 1
.text:0067EB43 mov esp, ebp
.text:0067EB45 pop ebp
.text:0067EB46 retn
.text:0067EB46 sub_67EB00 endp
If you read the function you can see it takes the packet data and does some processing with it (in sub_69FE30) which returns two unsigned ints via pointers to local variables. These variables are then passed to a second function. Because the morph is done on a single unit we can likely guess that that the function that performs the morphing is a member function of CGUnit_C (the class used to represent units in WoW). Based on this we can see that sub_67B2E0 is the function we want. sub_46D3C0 takes the two unsigned ints (along with some other things) and its return value is passed to sub_67B2E0 through ecx, this means that sub_67B2E0 is a class member function which confirms our suspicions about it being a member of CGUnit_C. We can now see that the two unsigned ints are actually the GUID of the unit that the game is going to morph and that the return value of sub_46D3C0 is the pointer to the unit which corresponds to that guid.
With this information we can now get the prototype and address for the function we need to call to redraw the object with its new display id. We know its done on units (just based on regular gameplay, but you can also tell from the string that is passed to the function which grabs the object pointer via guid), we also know the params we need to pass (we can just rip them right from the packet handler, we don’t really need to know or care what they’re actually for). From this we can derive:
void CGUnit_C::UpdateModel(unsigned int Unk1 = 1, unsigned int Unk2 = 1); // Address = 0×006800A0
It would be even more obvious what was going on if we looked inside the functions being called by the packet handler but its so simple that we don’t really need to.
Exercise for the reader: Find the function for the latest patch, and for bonus points, write a simple morph hack for the latest patch.
Pop quiz: You are the only person who can see the changed model. Why is this?
Very nice post, thanks alot. So.. where do I collect my points?
@sku
Please send an email to [email protected].
They’ll happily help you.
Nice guide indeed,
So here’s some traffic
Hey cypher, Nice work as usual
Also going with the whole cyphermorph theme why not do console commands next :D. Just a suggestion, i am sure whatever you pick it will be great
@jjaa
Nice idea. I might just do that.
Nice, maybe next time do an intro on object structure on wow since alot of people seem to ask about that in the forums, or how to make a simple fish bot would be a cool intro for people.