HowToHackD2 - Edition #1


File: HowToHackD2_Edition1.txt
Author: Jan Miller


-C Language basic knowledge
-x88 intel ASM basics
-Windows API / Windows programming basic knowledge
-OllyDbg (You can get it here: - but if you don't have it yet, you might aswell just skip this paper for the moment *g*)

.. and most importantly: Diablo II: Lord of Destruction with v1.11 Patch

General Introduction

This paper is going to be the first of a series of papers I plan to publish in the future. The series of papers has been designed for "regular coders" that are at interest to enhance their knowledge in the field of game-hacking. That means: IF you're already an experienced game-hacker, this paper might be quite a bore for you ;-). I plan on increasing the difficulty with the upcoming papers, so essentially my readers can grow and increase their qualities while we walk the road. I will also do my best at keeping the language as clear and straightforward as possible.

OK, now that we've got that stuff out of the way, let's get things 'rollin. Today we will take a deeper look at the red life-ball that displays your life when hovering the mouse above it. We will essentially try to reverse engineer Diablo II functions along the way - figure out how everything works - and to finish it off, write a hack that will ALWAYS display the life above the red life ball! :) This may seem like alot of work at first, but hopefully you will get the catch sooner or later. "Reverse engineering" isn't that hard, if you take your time and make sensable assumptions. In the end, you're basically re-constructing a crime, sort-of like Sherlock holmes :).

Chapter I - The Theory

Why do we need to find an entrypoint? Well, Diablo II has a few million lines of code, so we should try to imagine how the code-flow in Diablo II will probably be looking like, that draws the "Life: Min/Max" text above the red ball. In the end, all Diablo II functions will boil down to the basic system functions, so they will be our entry-point.

First, let's use our well-functional brain and gather some basic information, before starting (note: Using a moment of silence to think about the problem before tackling it, will be referred to as the "zen approach").

What do we know about text-drawing in Diablo II?

1) Diablo II is an international game that is sold in asian countries aswell. We assume that they handle strings in UNICODE format, to support countries such as "Korea"
2) In order for Diablo II to draw a text at a specific location, it needs to gather infos about the text it is drawing and where it is drawing. The infos gathered will be: "String length", "Screen Dimensions", "Text Color", etc.

What do we know about the red ball?

The most important fact about the life being drawn above the red ball is that the text is centered. The life of a player is variable, so the string length of the "Life: Min/Max" string will be variable aswell. So, in order for Diablo II to draw that string, it needs to somehow calculate its string-length - that is definate!

OK, so in this case I will use the fact that Diablo II needs to calculate the string length as our approach to get an entrypoint into a code-location that is near the "Draw Life to screen" function. This is the code-flow that I am assuming:

-> Diablo II gets mouse info
-> Diablo II checks if mouse is hovering the life ball
-> Diablo II calculates the players life
-> Diablo II sets up the output string (we will probably see a referrence to d2lang.dll here, as the Prefix may be "Life", "Leben" or any other language-dependent output)
-> Diablo II will print the string to the screen
---> Diablo II will calculate the string length in here, somewhere (THIS IS OUR ENTRYPOINT!!)
So basically, we will be breaking into Diablo II's code very deeply and use the stack to "walk back" to the callee functions. More to that later!

Chapter II - The Approach

So, fire up Diablo II and join a singleplayer game. That's my "preferred" environment for Diablo II reversing, as you will not "timeout" from a game if it's paused for too long (unlike, where you will be booted from the server for missing ping responses).

Alright, after you joined a singleplayer game and successfully attached OllyDbg to Diablo II, let your mouse hover over the life-bar so Diablo II's code passes the "IsMouseHoveringBall" check and actually draws the life. As soon as your mouse is hovered above the life-ball, use alt-tab to tab back to olly (be sure to not move your mouse before tabbing out of Diablo II). This is necessary, because we want Diablo II to save our mouse-position above the ball. The next time we maximize Diablo II the "IsMouseHoveringBall" check will pass and the code location's we breakpointed will be executed.

We will now try to find the "string-length" function as suggested in Chapter I and set an execution breakpoint there. You should have Diablo II minimized with your mouse hovering over the life-ball with ollydbg attached to the process before following these steps.

1) Press ALT-E to open the module list of Diablo II (lists all .dll files that Diablo II is using)
2) Find d2lang.dll and select it. Now Press CTRL-N to get a list of imported/exported function names
3) Find the mangled "strlen(UNICODE)" function and set an execution breakpoint (F2)

4) Now we are ready to maximize Diablo II, do so. You should be breaking at the strlen function now. Obviously, this function is being called by many different functions in Diablo, so you should press F9 (run) so many times until you actually see the "Life: Current/Max" string in the ECX register (used as parameter, if you take a close look at the function). You should eventually end up looking at something like this:

Note: It displays "Leben" instead of "Life" in the screenshot, because I am using the german d2lang.dll

5) Now take a look at the stack, it should be looking like this:

As we can see, the d2lang.strlen(UNICODE) function is being called by D2Win.6F8EDFB9, which is probably inside the "TextDraw" function (we are making this assumption, based on the code-flow we created in Chapter I). We can also see, that the *supposed* "TextDraw" function is being called by a function in D2CLIENT (the second return address in the stack, see picture above).

6) Right-click the second return address in the stack and select the "Follow in Disassembler" option in the drop-down menu. You should now be at this code location:

As you see, I've commented it abit. At this point (now that we know where the "TextDraw" for the life-ball hover-text is called) you should clear the breakpoint on the string function and set a new one abit futher above the new code location we found and repeat the process of maximizing Diablo II and breaking at the new location you chose. Do that until you understand what is going on and can make judgements about what the code is actually doing.

On a side note: We can derrive from the analysis of the d2lang.#10005 function call, that the function has this syntax:

typedef wchar_t* (__stdcall* fnGetLangStringByID) (DWORD dwID)
7) Now that we know what function actually draws the text, let's investigate how Diablo II actually gets the players life. Clear all your current execution breakpoints and make a new one at d2client.6FADD6C0 (the first instruction of the "DrawLifeAboveLifeBall" function). Restart the entire process (press F9 to let d2 execute itself, maximize d2 and then move your mouse over the life ball so olly breaks) so we can get a fresh start.

8) Now that we breaked at d2client.6FADD6C0 let's follow the code-flow and learn how Diablo II gets the players life. To do this, it'd be a good idea to get the hex-value of your current (and max) life. In this case, the hex value is: 0x2C (45 decimal). IF you really don't know what you're doing, you just follow the code flow by single-stepping (with F7) until the EAX register (which holds the return value of a function) returns our life, which is 0x2D (all values in ollydbg are displayed in hexadecimal). The first referrence of our current life is found here:

As you can see in the image, EAX holds the value 0x2D00 and is (a few instructions later) shifted to the right by 8 bits, which would result in 0x2D. IF you remember, our current life is 0x2D! BINGO!

We can now derrive from the analysis the following functions:

typedef DWORD (__fastcall* fnGetOwnPlayerStat) (DWORD StatID) //d2client.6FADCCC0 param passed in EAX!
typedef DWORD (__stdcall* fnGetMaxLifeFromUnit) (unit* ptrToUnit) //d2common.#10907
As you know, __stdcall passes the parameter on stack and __fastcall passes the first two parameters in ECX and EDX, the rest on stack. ODDLY enough though, the "GetOwnPlayerStat" function passes the parameter in the EAX register, which is very uncommon. This is the case, because Blizzard changed their compiler settings to call functions in a very optimized way (MSVC++ 7.0 might've been the cause). The only way we can actually call the function is to build a "wrapper" that adjusts the parameters for us. You'd call "GetOwnPlayerStat" like this:

DWORD __declspec(naked) __fastcall GetOwnPlayerStatWrapper(DWORD StatID) {
__asm { mov eax, ecx //first parameter is in ECX, so we move it to EAX, as the D2 function requires it
call fnGeOwnPlayerStat
9) Now that we have completed the analysis, let's move on to our actual goal: Making a hack that always enables the life display above the life-bar, irregardless if the mouse is hovering. We're taking the same approach as in step 5) - we will breakpoint at the first instruction of the "DrawLifeAboveLifeBall" function, let olly break at it, and check out the stack as to who is calling us. Then we will investigate at -what condition- we are being called. It turns out, that actually the function is called *no matter where our mouse is located* - so the conditional check has to be somewhere between the first instruction (d2client.6FADD6C0) and the call that gets our current life (d2client.6FADD742). A little "guessing work" will make us come up with these results:

10) Now that we know where the actual check happens, we can circumvent it easily. A possibility to do so is to add an unconditional jump at d2client.6FADD710, as shown in the image:

Chapter III - Writing the hack

Basically, to make the hack that displays the life above the life-bar at all times, we have to only change 2 bytes in Diablo II's code. We need to write 0x2BEB at d2client.6FADD6C0. You could achieve that by changing the memory protection of that memory page to EXECUTE_READ_WRITE and calling WriteProcessMemory - or just simply overwriting the location. Your .dll file could look like this (very dirty code):

	switch (dwReason) {
  	*(WORD*)&0x6FADD6C0 = 0x2BEB; //enable life display above life-bar at all times
	return TRUE;
This would actually do the job. Detectability is another issue, but that is not the aim of this essay. We might address that in another future edition.

About the Author

I am a german student and my name is Jan Miller. I've been developing Diablo II hacks for the past 1.5 years, and am still enjoying it. I have released more than a dozen different programs troughout various patches for free to the Diablo II community. You may always support me by sending a donation via paypal to the following e-mail: webmaster@****************

I hope you enjoyed this essay and I hope you have tasted and increased your interest in game hacking. :) This essay was written with a "few" beers in my body, so it is very possible that I've done some mistakes. Feel free to point them out!

Remember: Free information for the free mind!



BattleForums Senior Member
Anyway, <3 at Jan. Stuck ^^


Nice guide, wish I had something like this when I first started reversing stuff, running through examples really helps. Small tip - try saving OllyDbg and other "graphicless" screenshots in PNG format, they will be perfectly clear and much smaller filesize than JPG (which is designed for photos). See for more about this.


This kind of stuff rocks. I've always wanted to get into C-languages and hacking.. This helps out a lot! glad I came across this!


BattleForums Addict
Information is still valid, original post updated.


BattleForums Addict
If you want to work on them, and redo the both of them to make them more compliant and up-to-date, than by all means be my guest and I will sticky it. If not, than please refrain from posting in threads that are serveral years old and bringing them to the front if they are no longer valid or up-to-date.

Thread #2 was never updated and was tucked away, this thread is not included in previous comment as the information is up-to-date and still valid.


New Member
For some reason when I set the break point and maximize D2 it completely freezes and I can't do nothing about it. Is there something I should do?

And btw, I don't know if that's for everybody, but if there were any screenshots in this post, they are not there.