Diablo II

Code Editing Tutorial

For Windows

v2.01

Original version by Sir_General

Updated for v1.10+ by Myhrginoc

 

 

 

 

Introduction

 

Welcome to the Diablo II code-editing tutorial.  This tutorial will, hopefully, introduce you to the basics of code-editing a DLL for any game, and specifically, for Diablo II. The tutorial won’t go into the fine points about Diablo II function and structure conventions, but there are many references at the Phrozen Keep which get into these topics.

 

There are a number of different ways to modify a program.  This tutorial looks specifically at hex editing and disassembling the DLLs of Diablo II.  It starts with the assumption that you know nothing about hex editing, hexadecimal, ASM, or the Diablo II DLLs.  It will, therefore, cover all of those topics, starting with hexadecimal.

 

We will use a few pieces of software to perform the tasks but fear not because each item (except for Diablo II) is free of charge.  To complete everything in this tutorial, obtain all of the following from the listed website:

 

XVI32 Hex Editor

This is the freeware hex editor of choice.  It has a fairly easy to use interface and the price is right.  You can get this at http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm.

 

OllyDbg

OllyDbg is a freeware hex editor, dissassembler, assembler, and debugging tool.  It has a number of very advanced features and a very user-friendly interface.  All free of charge.  This one can be obtained from http://www.ollydbg.de/.

 

Windows Calculator

While many programs have ways to convert between decimal and hexadecimal, I haven’t found any that can do addition or subtraction in hexadecimal except for the standard Windows Calculator.  If you don’t have a shortcut for it, look in your Windows directory or perhaps your Windows\System32 directory for CALC.EXE.

 


Versions of Diablo II DLLs

 

The first thing you need to know is which version you are working with.  If you don’t already know, the tables below will help you identify them.  This information can be found by right-clicking the DLL name in Windows Explorer and selecting Properties.  For now, ignore the last column named Image Base.

 

Each version is compiled anew (with the possible exception of v1.09 to v1.09b).  This means the code will be different, in some cases radically different (as with v1.11 and v1.11b), so what is shown here for a particular version may look different and/or be in a different location in any other version.

 

D2Client.dll

Size

Modified Date

Image Base

1.07

1,175,609 bytes

Tuesday, June 19, 2001, 10:20:08 PM

6FAD0000

1.08

1,126,457 bytes

Monday, June 18, 2001, 6:09:08 PM

6FAD0000

1.09

1,138,745 bytes

Thursday, August 16, 2001, 3:00:18 PM

6FAA0000

1.09b

1,138,745 bytes

Thursday, August 16, 2001, 3:00:18 PM

6FAA0000

1.09d

1,134,644 bytes

Thursday, November 29, 2001, 6:45:20 PM

6FAA0000

1.10 beta

1,073,204 bytes

Thursday, July 03, 2003, 10:21:10 AM

6FAA0000

1.10s beta

1,073,204 bytes

Sunday, July 27, 2003, 9:29:34 PM

6FAA0000

1.10 final

1,085,505 bytes

Thursday, October 02, 2003, 2:46:22 PM

6FAA0000

1.11

1,093,632 bytes

Tuesday, July 26, 2005, 3:16:18 PM

6FAB0000

1.11b

1,093,632 bytes

Tuesday, August 16, 2005, 5:02:32 PM

6FAB0000

 

D2Common.dll

Size

Modified Date

Image Base

1.07

700,473 bytes

Tuesday, June 19, 2001, 10:20:08 PM

6FD60000

1.08

688,185 bytes

Monday, June 18, 2001, 6:03:14 PM

6FD60000

1.09

692,281 bytes

Thursday, August 16, 2001, 2:52:30 PM

6FD40000

1.09b

692,281 bytes

Thursday, August 16, 2001, 2:52:30 PM

6FD40000

1.09d

692,276 bytes

Thursday, November 29, 2001, 6:32:36 PM

6FD40000

1.10 beta

708,660 bytes

Thursday, July 03, 2003, 10:16:48 AM

6FD40000

1.10s beta

712,756 bytes

Sunday, July 27, 2003, 9:25:14 PM

6FD40000

1.10 final

725,057 bytes

Thursday, October 02, 2003, 2:37:54 PM

6FD40000

1.11

679,936 bytes

Tuesday, July 26, 2005, 3:10:32 PM

6FD50000

1.11b

679,936 bytes

Tuesday, August 16, 2005, 4:58:16 PM

6FD50000

 

D2Game.dll

Size

Modified Date

Image Base

1.07

999,479 bytes

Tuesday, June 19, 2001, 10:20:08 PM

6FC60000

1.08

1,003,575 bytes

Monday, June 18, 2001, 6:05:12 PM

6FC60000

1.09

1,028,151 bytes

Thursday, August 16, 2001, 2:55:36 PM

6FC30000

1.09b

1,028,151 bytes

Thursday, August 16, 2001, 2:55:36 PM

6FC30000

1.09d

1,028,146 bytes

Thursday, November 29, 2001, 6:37:06 PM

6FC30000

1.10 beta

1,159,218 bytes

Thursday, July 03, 2003, 10:18:24 AM

6FC30000

1.10s beta

1,163,314 bytes

Sunday, July 27, 2003, 9:26:48 PM

6FC30000

1.10 final

1,159,231 bytes

Thursday, October 02, 2003, 2:41:10 PM

6FC30000

1.11

1,138,688 bytes

Tuesday, July 26, 2005, 3:12:56 PM

6FC20000

1.11b

1,138,688 bytes

Tuesday, August 16, 2005, 5:00:00 PM

6FC20000

 


Binary/Decimal/Hexadecimal

 

In computers, everything is in binary.  Even the words you are reading are a series of 0s and 1s.  However, this would be very inconvenient for editing purposes because it takes a lot of space to display all those 0s and 1s.  This is why the 0s and 1s are commonly grouped into sets of eight.  You’ll never find anything that deals with a single 0 or 1 (which is called a bit).  It will always been a group of 8 (a BYTE), a group of 16 (a WORD), a group of 32 (a DWORD) or a group of 64 (a QWORD).  We don’t use qwords directly in Diablo II so we’ll ignore this last data size, although there is an important exception we will visit later.  We’ll look at the differences between the others later.  For now, it’s important to learn how to read a byte.

 

Numbers are represented using the groups of bits we mentioned about.  They are represented in this fashion:

 

0000 0000 Binary                    000 Decimal                 00 Hexadecimal

0000 0001 Binary                    001 Decimal                 01 Hexadecimal

0000 0010 Binary                    002 Decimal                 02 Hexadecimal

0000 0011 Binary                    003 Decimal                 03 Hexadecimal

0000 0100 Binary                    004 Decimal                 04 Hexadecimal

0000 0101 Binary                    005 Decimal                 05 Hexadecimal

0000 0110 Binary                    006 Decimal                 06 Hexadecimal

0000 0111 Binary                    007 Decimal                 07 Hexadecimal

0000 1000 Binary                    008 Decimal                 08 Hexadecimal

0000 1001 Binary                    009 Decimal                 09 Hexadecimal

0000 1010 Binary                    010 Decimal                 0A Hexadecimal

0000 1011 Binary                    011 Decimal                 0B Hexadecimal

0000 1100 Binary                    012 Decimal                 0C Hexadecimal

0000 1101 Binary                    013 Decimal                 0D Hexadecimal

0000 1110 Binary                    014 Decimal                 0E Hexadecimal

0000 1111 Binary                    015 Decimal                 0F Hexadecimal

0001 0000 Binary                    016 Decimal                 10 Hexadecimal

 

And this continues until you reach:

 

1111 1111 Binary                    255 Decimal                 FF Hexadecimal

 

Notice the following in each.  Binary only uses 0s and 1s, like I said before.  Decimal is what you are familiar with and know how to read.  Hexadecimal is like decimal until you reach 10, and then it starts using the letters A through F.  You won’t usually have to convert between binary and decimal when you are code editing, but I’ll cover it anyway because you never know.

 

 

To convert from binary to decimal, you need to follow the following series of steps (also known as an algorithm, if you haven’t encountered that term before).  You need to write a total somewhere.  The total starts at 0.  Look at the right most number.  If it is 0, go to the next number.  If it is 1, then you need to add 2N to the total.  N will depend on what part of the binary number you are on.  For the right most number, N is 0.  For the one next to it, N is 1.  For the one next to that one, N is 2.  After that, N becomes 3.  Then N becomes 4.  And so on until you’ve gone through all of the numbers in the binary number.  For reference, here is 2 raised to the most common powers.

 

20 = 1

21 = 2

22 = 4

23 = 8

24 = 16

25 = 32

26 = 64

27 = 128

 

Now lets do an example.  Convert 0010 1001 to decimal.

 

We start by writing down our total.  TOTAL = 0.

And write down what N is.  N = 0.

 

0010 1001

 

Look at the right most number of the binary number.  It’s a 1, so we add 2N to our total.  Right now, N is 0, so we add 20, which is 1.  So now TOTAL = 1.

 

Move to the next number, and don’t forget that N increases.  N = 1.

 

0010 1001

 

This is a 0, so we skip it.

Move to the next number and increase N.  N = 2.

 

0010 1001

 

This is another 0, so we skip it.

Now N = 3.

 

0010 1001

 

Now we have another 1, so we add 23 to the total.  23 = 8, so now the TOTAL = 9.

After this, N = 4.

 

0010 1001

 

We have a 0, so we skip it.

Now N = 5.

 

0010 1001

 

This is a 1, so we add 25 to the total.  25 = 32, so now the TOTAL = 41.  As you can see, the other two numbers after this are 0, so we can stop here.  The result:

 

0010 1001 Binary                    41 Decimal

 


If you didn’t catch any of that, there’s an easier method to converting.  It’s called the Windows Calculator.  If you aren’t a regular user, the first time you open it you will probably see the smaller format shown on the left below.  We will change it to the more useful Scientific Calculator as shown at right.  Turn on Digit Grouping as well, it makes identifying number parts easier.

 

  

 

 

Now choose the Bin(ary) option and enter your binary value.  Also note, in later versions of Windows Calculator, you can display values in qword, dword, word and byte sizes.  Because we are using 32-bit processing in a Pentium-series processor, select dword as your data size.  The next screen shows the controls to use.

 

 

Now switch back to Dec(imal) and the binary value will change as well:

 

 

In the event that you need to convert from binary to hexadecimal, you can just choose the Hex(adecimal) option instead of the Dec(imal) option.  We will see how you have to do it by hand, converting from decimal to hexadecimal and vice versa.  Before you can convert from decimal to hexadecimal, you need to know what all those letters in hexadecimal mean.


Here’s the conversion:

 

00 Decimal                   0 Hexadecimal

01 Decimal                   1 Hexadecimal

02 Decimal                   2 Hexadecimal

03 Decimal                   3 Hexadecimal

04 Decimal                   4 Hexadecimal

05 Decimal                   5 Hexadecimal

06 Decimal                   6 Hexadecimal

07 Decimal                   7 Hexadecimal

08 Decimal                   8 Hexadecimal

09 Decimal                   9 Hexadecimal

10 Decimal                   A Hexadecimal

11 Decimal                   B Hexadecimal

12 Decimal                   C Hexadecimal

13 Decimal                   D Hexadecimal

14 Decimal                   E Hexadecimal

15 Decimal                   F Hexadecimal

 

Now that you know what the letters mean, let’s look at the general idea behind a conversion.  You take your decimal number and divide it by 16.  Make sure you know what the remainder is.  Once you do this, you take the remainder and write that down.  Remember that if the remember is between 10 and 15 that you use the letters A through F.  Now you take what is left and divide that by 16.  Get the remainder for that and write that down.  Continue to do this until you have nothing left to divide.  Let’s look at an example.

 

Convert 217 to hexadecimal.

 

We start by dividing 217 by 16.  This gives us 13 R(emainder) 9.  So we write the 9 down.

 

Now divide 13 by 16.  This gives us 0 R 13.  Now we write down D.  This gives us D9.  Remember to use the letters from above.

 

Since we have 0 left, we’re finished.  The result:

 

217 Decimal                 D9 Hexadecimal

 

If you didn’t catch that, you can use the calculator.  I won’t go through it again.  The only difference is that instead of switching between Bin(ary) and Dec(imal) you switch between Hex(adecimal) and Dec(imal).

 

It’s also important to learn how to convert from hexadecimal to decimal because you’ll be doing it a lot.  This is a lot like converting from binary to decimal.  You start with the right most number and multiply it by 16N.  You add this to your total.  Then you move on to the next number and multiply it.  Just like in binary, N starts at 0 and increase every number.  So the first number is 0, the second is 1, the third is 2, the fourth is 3, and so on.  Also, if you have one of the letters instead of a number, just use its decimal equivalent.  So if you come across an A, you multiply 16N by 10.  Let’s look at an example.

 

Convert 3F to decimal.

 

We start with TOTAL = 0 and N = 0.

Our first number is F, or 15.  Multiply this by 160, which is 1.  So now TOTAL = 15.

Increase N, so N = 1.

Our next number is 3, so we multiply 161, which is 16, times 3.  That’s 48, so we add 48 to the TOTAL.

Now TOTAL = 63.

That’s the last number, so the result is:

 

3F Hexadecimal           63 Decimal

 

Once again, the calculator can be used instead of doing it yourself.  The upper six hex numerals (red box) are enabled in hex mode.  Compare this screen with the binary screen above, where only the 0 and 1 number keys are enabled.

 

 


Hex Editing with XVI32

 

Run XVI32 and look at the XVI32 interface. 

 

 

There are three main sections to the application screen.  The left section is the file offset, the center section is the raw hexadecimal file content, and the right section is an ASCII version of the file content.  While these can be arranged in many ways, the best arrangement (following compiler alignment convention) is 17 rows and 16 columns, so Page Up and Page Down move you 0x100 (256 bytes) at a time.  So we will start with the options found in the Tools menu as shown above.

 

 

We will begin by opening a copy of the file D2Game.dll from your Diablo II directory.  (It is a bad idea to work on original files.)  This discussion assumes you have patched the game to the latest version, v1.11b.  The ASCII display of the file is used to help identify strings within the file.  A string is basically just a word or a sentence or some other readable thing.  You’ll notice that the highlighted section reads “This program cannot be run in DOS mode.”  This is a string, which includes the trailing 00 byte, also known as a null terminator.  We never would’ve noticed it without the ASCII display, either.

 

 

At the bottom of the screen is the status bar.  The leftmost box shows the current address (or more correctly, the current offset); it tells you where you are in the file.  Depending on whether you have the center pane active (as above) or the right panel active (see previous page), the offset will be displayed as a hexadecimal or a decimal number.  Almost every offset/address you see will be in hexadecimal, so you may want to work primarily in the center pane.  Beside that is the decimal value of the current byte you have selected.

 

Next to the offset is the decimal version of the byte selected.  We have 54 selected, and its decimal value is 84.  Pretty handy, no?

 

Finally, you have insert/overwrite mode.  In insert mode, the data you put in is added and all the rest of the data comes after it.  This increases the location of every byte after it by 1.  You should never use insert mode.  Make sure it says overwrite.  In overwrite mode, the data is replaced, not inserted.  You can change modes by pressing the “Insert” button on your keyboard or in the “Tools” menu.


Now let’s navigate in the file.  We will start with going to specific locations.  Begin with the Address menu and select Goto:

 

  

 

 

In this dialog, you insert the address/offset you want to goto and push OK.  The editor will automatically go there so you don’t have to search for it.  On the other hand, you may know what you want to change, but not know where it is.  For that we use the Search sequence below:

 

 

 

Now that we’ve seen the basics of the XVI32 Hex Editor, let’s look at an example.  We’re going to change the experience awards given by the Ancients quest in Act 5.  We know the award values are as shown in the first two columns:

 

Normal difficulty

1,400,000

0x00155CC0

0xC05C1500

Nightmare difficulty

20,000,000

0x01312D00

0x002D3101

Hell difficulty

50,000,000

0x02625A00

0x005A6202

 

One peculiarity of Intel machines is, when constants are included as part of code, the byte order is reversed.  So even though a straightforward hex conversion gets us the numbers in the third column, we need to reorder the bytes and search on the numbers in the last column.  The first value is shown in the sample Search dialog above.


We find one occurrence of the first value at offset 0x52FC9.  The full value is shown in red here.  Two lines are included, in which you will find the Nightmare value at 0x52FDA and the Hell value at 52FD3.  But for now we will just focus on the Normal value.

 

 

Let’s change the Normal award to 2,000,000.  Converting the new value to hex we get 0x001E8480, and reversing bytes we end up with 0x80841E00.  Make sure you’re in overwrite mode, this is very important.  Now click on the leftmost byte and type 80 84 1E 00.  You should see:

 

 

You have just edited the Diablo II code to make it so the Ancients give you 2,000,000 experience when you kill them all in Normal difficulty.  It is as simple as that.  To keep this change, save the file before leaving it.

 

Here is another example, in d2common.dll v1.11b.  This time we will change the gamble cost function so all items use the Gamble Cost field in weapons.txt, armor.txt or misc.txt.  Currently only rings and amulets do this, but it is simple to change the conditional check into a forced behavior.  Load the dll and go to offset 0x72E74 (as shown in the Goto command example above); you should find the sequence:

 

 

Make sure you are in overwrite mode and replace these bytes with

 

 

Now every item will use the Gamble Cost field instead of calculating a gamble fee based on character level.

 

Supposing you are following a discussion, and somebody says a value might be at a particular offset but the value itself is not given?  You can find out what the number is by using one of the tools in XVI32, Decode Number.  This is located as shown:

 

 

Select the first byte of a number, which is always the offset of the value, and decode it.  You will see several values, the correct one being determined by context but usually it is the longint value.  You will see both hex and decimal forms of the decoded value.  The sample below confirms what we discussed when we changed the Ancients award.

 

 

Right now you are probably wondering, “how the heck do I know what should be changed?”  Hex editing is suitable for following canned recipes for small code edits, or changing the data sections of a dll.  You will see that in many cases people will post both hex and assembly changes together, so you can find quite a few changes to try without learning assembly language.  But for more extensive code edits and research, you need to bite the bullet and learn assembly language, which we will tackle in the next section.

 


Basic Assembly, Intel Style

 

Assembly language (sometimes called ASM for short) is a very complicated thing, and there is no way this tutorial can cover it all.  I will, however, go into some of the basic, but important, concepts you’ll need to know to get started.  This includes registers, the stack, important commands, and a few other things.

 

Let me start by making a quick note that ALL ASSEMBLY VALUES ARE IN HEXADECIMAL.  So I hope you were paying attention when you went through the hex sections above.  That said, it’s on to registers.

 

While there are a number of registers in assembly, we’re just going to concentrate on the 32-bit registers for now.  First, let’s look at what a register is: a location within the processor itself that can hold a 32-bit number (including leading 0’s as needed).  That’s all it does, but it works faster than anything else involving data storage.  We use registers because a processor can only deal with so many numbers at a time.  You can imagine how many numbers Diablo II has in memory.  Well, registers are basically the numbers that we are using at the moment.

 

Each register has a purpose, supposedly, but you’ll find that some can be used for mostly anything while others should never be touched by you unless you know exactly what you’re doing.  Here are the registers you should know:

 

EAX – Accumulator – The register is useful for just about anything you want.  EAX is sometimes used as the return value of functions.

EBX – Base – This is another register that can be used for just about anything.

ECX – Counter – You can use the counter register for anything.  Just know that it is automatically used by the REP and LOOP instructions.

EDX – Data – Another all-purpose register.

 

ESI – Source Index – This register can be used for plenty of things, but it is commonly used with data structures and string manipulation.

EDI – Destination Index – This is like ESI.  It can be used for most anything, but is usually used with data structures and string manipulation.

 

EBP – Base Pointer – This register has lots of purposes, as well.  It’s commonly used as a pointer to the stack.

 

ESP – Stack Pointer – This register is almost explicitly used as the stack pointer.  Unless you know exactly what you’re doing, don’t touch this or you run the risk of corrupting the stack.

 

EIP – Instruction Pointer – This register points to the current instruction to execute.  You can’t mess with it directly, and you never should.  This will be properly changed by the processor with CALL, JMP, and RETN statements.

 

The registers you’ll be using most are the EAX, EBX, ECX, and EDX registers.


Now let’s take a look at the stack.  Each program has a stack, and this is one of the most important parts to the program.  Few things can come as close.  A stack is often referred to as a “last in, first out” system.  What this means is that the last item put on the stack is the first item to come off the stack.  To prove this, look at the below example of books:

 

 

The important thing to learn here is that in a stack, the first item on is the last item off.

 

Before we continue on, it’s a good idea to understand just how important the stack is.  Let’s say that you were performing a task with the books above and you expected everything to go just in the order that it does above.  What would happen, however, if the red book weren’t put on?  When you went to pick up the red book, you’d get the yellow.  When you went to pick up the yellow, you’d get the green.  And when you went to pick up the green, you’d get the blue.  But what happens when you get to picking up the blue?  Well if this is a program, chances are you crashed way back when you picked up the yellow instead of the red.  And if not then, one of the other times.  If you did manage to make it through, well then you crash now.  In any case, you crash, which is bad.  So make sure you don’t mess up the stack by either forgetting to put something on or taking something off you shouldn’t or even putting things on in the wrong order.  It doesn’t take a lot to crash a program if you corrupt the stack.  This is also the reason why you shouldn’t mess with ESP (Stack Pointer) unless you know what you’re doing.


With that out of the way, we can begin to look at some basically assembly instructions, starting with two that work with the stack, PUSH and POP.

 

Now that we’ve seen the registers and the stack, we need to learn how the two work together.  If you haven’t noticed, we only have about 7 registers that we can use as much as we want.  So what happens we need to temporarily used a register for something else and then get its value back?  Well, we use the stack.  We put it on the stack and then take it off again.  To put something on the stack, we use the PUSH instruction.  When we do this, we say we are pushing something onto the stack.  The PUSH instruction looks like this:

 

PUSH EAX

PUSH EBX

PUSH ECX

 

The stack starts at the top of the memory block allocated to it and grows down.  So each time you execute a PUSH you also subtract 4 from the current value of  the ESP register, which always points to the bottom of the downward-growing stack.

 

The following segment of assembly will put the values in EAX, then EBX and finally ECX onto the stack.  We can also push things beside the registers.  We can do numbers as well.

 

PUSH 5

PUSH FF

 

That segment of assembly will put the value 5 and then 255 onto the stack.  When you do a PUSH instruction, whether it is on a register, a number, or something else, all you are putting onto the stack is a number, and that number is always a DWORD.  It will be padded with leading zeros if necessary.  And just because you used the PUSH EAX instruction this doesn’t mean that when you take this off the stack you have to take it off and put it in EAX again.  It can be put in another register as well.

 

Once you’ve put things onto the stack, you have to know how to take them off.  You do this with the POP instruction.  This is usually called popping something off the stack.  This works almost exactly like PUSH.  POPs are also always DWORDS.  They look like this:

 

POP EAX

POP EBX

 

Pretty similar, no?  This segment will take the top value on the stack and put it in EAX.  Then it takes the next value and puts that in EBX.  Each POP also adds 4 to the contents of the ESP register, and the stack shrinks up towards its top.  Unlike PUSH, though, you CANNOT do something like:

 

POP 5

 

This doesn’t work.  You can POP to a location in memory, although you won’t see this very often (at least not in Diablo II from what I’ve seen).  But by far the most useful is popping to a register.

 

It is important enough to reiterate, every PUSH and POP modifies the stack and the ESP.
So far we have dealt with registers and stacks.  Stacks are defined in memory, but we can use a lot more in memory than what is allocated on a stack.  In fact, memory contains all the code, data, stack, dynamic structures and everything else related to a program when it is running.  And every item in memory in a program has a location, which we call a memory address, or just address for short.  In a 32-bit machine you can have addresses between 00000000 and FFFFFFFF.  Windows reserves many addresses for its own functions, so for our purposes the top address is 6FFFFFFF.  And 00000000 itself is given a special purpose as a null pointer, so it cannot be used as a valid address.  But that still leaves us with over 1.5GB to play with!  We access any location with a pointer reference, in the form

 

{data size} PTR DS:[address]

 

What that code says is that we want to get a value of a specific size (byte, word, dword as mentioned earlier) at the given location in memory.  The DS: refers to a special register, called a segment register, which used to be very important in Intel coding.  But with 32-bit processors, huge quantities of RAM and all recent versions of Windows, nobody does segment-based programming anymore.  In Diablo II, many references include the DS register, so you need to recognize it when you see it in Ollydbg.  And some references use the SS segment register, but again it is not important for our discussion.

 

It is extremely important to keep a clear distinction in mind between a memory address and the contents of memory at that location.  The sample table below has a column for addresses and a column of hypothetical values.

 

 

Here is an example of how we reference memory.  Let’s say that E2 is actually a dword, stored in reverse byte order, and we want to use it.  Our pointer reference would be:

 

DWORD PTR DS:[0029800C]

 

In the same way, if we want to retrieve the value at 00298004 as a word, or at 0029801C as a byte, we would use these pointer references:

 

WORD PTR DS:[00298004]

BYTE PTR DS:[0029801C]

 

Because we are working with 32-bit systems, there is no equivalent qword pointer reference.


Now let’s look at moving values around between registers and memory.  Let’s say we wanted to move the value in EDX to EAX.  We could do the following:

 

PUSH EDX

POP EAX

 

This would put the value in EDX onto the stack and then remove it and put it in EAX.  But it is relatively inefficient, and it modifies the stack when we don’t need to.  There is a better way to do this, using the MOV instruction.  MOV stands for move (big surprise) and is used to move a value between registers, or between a register and a location in memory.  To transfer a value from EDX to EAX with this instruction, we would write:

 

MOV EAX, EDX

 

This reads as “move EDX into EAX”.  In a MOV instruction there are always two things separated by a comma.  In this case they are EAX and EDX.  The first thing is the destination, or the thing we want the value to be placed into.  The second thing is the source, or the value we want placed in the first thing.  We can also use MOV to move numbers into a register:

 

MOV EAX, 30

 

This moves the value 48 into EAX (remember in hexadecimal 30 = 3 * 16, not 3 * 10).  We can’t do the opposite, though, and say:

 

MOV 30, EAX

 

That doesn’t work, because 30 is not a destination.

 

We can also use move into and out of locations in memory.  Here we use the pointer references we mentioned earlier.  Returning to the memory table above, let’s move the dword 000000E2 to EAX:

 

MOV EAX, DWORD PTR DS:[ 0029800C]

 

Conversely, we can move from a register to a memory location:

 

MOV DWORD PTR DS:[ 00298030], EAX

 

But you cannot transfer information using a pointer reference as a destination and as a source, so the intended instruction…

 

MOV DWORD PTR DS:[ 00298030], DWORD PTR DS:[ 0029800C]

 

…must be broken into two steps, using an available register as an intermediate stop:

 

MOV EAX, DWORD PTR DS:[ 0029800C]

MOV DWORD PTR DS:[ 00298030], EAX

 
What about smaller data sizes?  We have discussed 32-bit registers, but we know data can be bytes, words or dwords in memory, not to mention strings and structures.  You cannot directly say

 

MOV EAX, BYTE PTR DS:[ 0029801C]

 

You have to use one of the special MOV instructions, or use a register reference that fits the data size.  There are two instructions for moving smaller data to a register.  Which one you choose depends on whether your application is signed or unsigned.  The unsigned form is

 

MOVZX {register}, BYTE PTR DS:[location]

MOVZX {register}, WORD PTR DS:[location]

 

The MOVZX copies the data into the lower bits of the register, and pads the higher bits with 0.  So if we say, load EAX with the byte value 4D at 0029801C, the instruction would yield

 

EAX = 00 00 00 4D

 

Similarly, loading EAX with the word value F338 at 00298004, the instruction would yield

 

EAX = 00 00 F3 38

 

The signed form is MOVSX, and behaves similarly.  But instead of always padding higher bits with 0, it does this only if the source number is positive, and it pads with 1 if the source number is negative.  This has to do with the fact that the most significant bit of any number is the sign bit.

If the byte number we used in the MOVZX discussion had been a negative value, its representation would be B3.  The word value is already a negative number.  So the results of MOVSX with these two values would be

 

EAX = FF FF FF B3

EAX = FF FF F3 38

 

It looks like the MOVZX instruction takes the absolute value of the data, but that is a wrong interpretation as 0000F338 is definitely not equal to abs(FFFFF338).

 

Now let’s look at the other way to move smaller data into and out of memory.  MOVZX and MOVSX are one-way transfers, they do not work to put a register value into memory.  In order to handle this, the 32-bit registers have subdivisions we can refer to directly.  If we say EAX has four bytes as below (MSB being the most significant byte, LSB being the least significant byte)…

 

EAX = MSB 2SB 3SB LSB

 

…then we can refer to the LSB value directly as byte-size register AL

…and we can refer to the 3SB value directly as byte-size register AH…

…and we can refer to the 3SB-LSB word value directly as word-size register AX…

…but we cannot refer directly to the MSB or 2SB bytes, or to the MSB-2SB or 2SB-3SB words.

 

EBX can be subdivided into BL, BH and BX; ECX into CL, CH and CX; and EDX into DL, DH and DX.  The specific bytes referenced by these subdivisions are exactly as with those of EAX.

The remaining general-purpose registers, ESI, EDI and EBP, do not have byte-wide subdivisions.  You can refer to the 3SB-LSB word for the first three as SI, DI and BP respectively.  You can also refer to the 3SB-LSB word of ESP as SP, but this is more risky since we are now looking at the pointer to stack addresses itself.  It is very important to manipulate this last register in dword increments only!

 

That is a lot of discussion about register organization.  Let’s look at an example.  If we loaded EAX with the value 0x4F3D1B7A, we would see

 

AL = 7A

AH = 1B

AX = 1B7A

 

Now let’s put this all together with moving data into and out of memory.  We return to the example used in the MOVZX and MOVSX discussions, but now we load into appropriately-sized register subdivisions…

 

MOV AL, BYTE PTR DS:[0029801C]

MOV CX, WORD PTR DS:[00298004]

 

…which puts 4D into AL and F338 into CX respectively.  If both registers were zero beforehand, you would see

 

EAX = 00 00 00 4D

ECX = 00 00 F3 38

 

But in neither case were the other bytes actually zeroed out by the MOV instruction, unlike what was done by the MOVZX instruction.  If those bytes were nonzero before, they remain nonzero afterward.

 

You can also load memory from a register the same way.  Just write

 

MOV BYTE PTR DS:[0029801C], AL

MOV WORD PTR DS:[00298004], CX

 

We saw before that data can be moved between the stack and either registers or memory, but doing so with PUSH and POP alters the stack itself and shifts the stack pointer.  We might want to read data from the stack, or write to some location not at the stack pointer.  This is how local variables in a procedure are implemented in assembly language, and how more than one item can be passed from a procedure to the procedure that references it.  These take the form

 

MOV {register}, DWORD PTR DS:[ESP+{offset}]

 

MOV DWORD PTR DS:[ESP+{offset}], {register}

 

We will discuss this use of the MOV instruction in much greater detail later on.
Now we will see what can be done with these numbers, starting with mathematical instructions.  Let’s look first at the ADD instruction.  This is pretty self exclamatory.  This adds a value to the given register.  For example:

 

ADD EAX, 1

 

This adds 1 to EAX.  We can also say something like:

 

ADD EAX, EDX

 

This adds the value of EDX to EAX.  Not so difficult to understand, now is it.

 

You cannot say ADD 1, EAX any more than you can say MOV 1, EAX.  Destinations must always be containers such as registers and memory locations; they cannot be constants.

 

If there is an ADD instruction, then it follows that there should be one for subtraction.  This is called the SUB instruction, and works just like the ADD instruction:

 

SUB EAX, 1

SUB EAX, EDX

 

Nothing surprising there.

 

Following along the with the ADD instructions is the INC instruction.  This increases the value of the given register by one.  This is simply:

 

INC EAX

 

The above statement is the same as saying:

 

ADD EAX, 1

 

Only it is smaller (one byte instead of three) and executes faster.  You’ll see this a lot, so remember it.

 

And if there’s an INC instruction, there is obviously one to decrease the value of a register by one.  This one is called the DEC instruction.  It works just the same:

 

DEC EAX

 

Which is like saying:

 

SUB EAX, 1

 

Not very difficult to understand.


What about multiplication and division?  Instructions exist to handle them, but there are two variations of each and they have unusual behavior based on what we have discussed so far.  We will start with multiplication: there are two instructions, MUL and IMUL.  And we will use the Intel convention of referring to register sizes (in bits) as r8, r16, r32 and memory location sizes as m8, m16 ands m32.  This convention also includes r/m8, r/m16 and r/m32 to mean either register or memory location of stated size.  Numeric constants are referred to as immediate numbers: imm8, imm16 and imm32.

 

MUL is an unsigned operation that always uses the EAX register as one of the two sources and also uses the EAX and EDX registers to receive the result.  When preparing for a multiplication, it is best to be sure these registers do not contain information that should not be lost, or (in the cases for smaller data sizes) contains data in unaffected portions that might skew the results.  Depending on data size, we see these effects:

 

  1. For byte-size numbers, MUL [r/m8] means AL * [r/m8] à DL:AL, where the high byte of the answer goes to DL and the low byte of the answer goes to AL.  The rest of EDX and EAX are unchanged.

 

  1. For word-size numbers, MUL [r/m16] means AX * [r/m16] à DX:AX, where the high word of the answer goes to DX and the low word of the answer goes to AX.  The rest of EDX and EAX are unchanged.

 

  1. For dword-size numbers, MUL [r/m32] means EAX * [r/m32] à EDX:EAX, where the high dword of the answer goes to EDX and the low dword of the answer goes to EAX.

 

IMUL is a signed multiplication that can be used in three ways.  The first form is very similar to the previous instruction, using AL, AX or EAX as one of the two sources and the results always end up in EDX:EAX.  The other two forms allow you to use another register than EAX as one source and the destination, but if the result exceeds the data width then the excess bits are lost.  Also note that the register or memory location used as a second source can also be an immediate constant, either one byte or matching the width of the destination.  So there are actually three sub-form 2 and two sub-form 3 versions.  In new code it might be hard to see a distinction, but when looking at existing code it makes a difference in the number of bytes used for the immediate value.

 

Form 2 for word-size numbers, MUL [r16],[r/m16] means [r16] * [r/m16] à [r16]

Form 2 for dword-size numbers, MUL [r32],[r/m32] means [r32] * [r/m32] à [r32]

Form 2 for word-size numbers, MUL [r16],[ imm8] means [r16] * [imm8] à [r16]

Form 2 for dword-size numbers, MUL [r32],[ imm8] means [r32] * [imm8] à [r32]

Form 2 for word-size numbers, MUL [r16],[ imm16] means [r16] * [imm16] à [r16]

Form 2 for dword-size numbers, MUL [r32],[ imm32] means [r32] * [imm32] à [r32]

 

Form 3 for word-size numbers, MUL [r16],[r/m16],[imm8] means [r/m16] * [imm8] à [r/16]

Form 3 for dword-size numbers, MUL [r32],[r/m32] ,[imm8] means [r/m32] * [imm8] à [r/32]

Form 3 for word-size numbers, MUL [r16],[r/m16],[imm16] means [r/m16] * [imm16] à [r/16]

Form 3 for dword-size numbers, MUL [r32],[r/m32] ,[imm32] means [r/m32] * [imm32] à [r/32]

 

Whew!  That is a lot of work for multiplication, no?  We better look at some examples.


Let EDX = 0 and EAX = 0 before anything happens.  If we execute the sequence…

 

MOV ECX,6

MOV EAX,12

MUL CL

 

…then we will multiply 0x06 * 0x12 and get 0x006C.  In DL we would have 0x00 and in AL we would have 0x6C.  But if we have…

 

MOV ECX,6

MOV EAX,12

MUL CX

 

…then we will multiply 0x0006 * 0x0012 and get 0x0000006C.  In DX we would have 0x0000 and in AX we would have 0x006C.  But if we have…

 

MOV ECX,6

MOV EAX,12

MUL ECX

 

…then we will multiply 0x00000006 * 0x00000012 and get 0x000000000000006C.  In EDX we would have 0x00000000 and in EAX we would have 0x0000006C.

 

Now we look at examples of IMUL.  The first form looks like the preceding examples, so we won’t repeat them here.  But the other forms might look like…

 

MOV ECX,6

MOV EAX,12

IMUL CX,AX

 

…where we would multiply 0x0006 * 0x0012 and get 0x006C in CX.  But if we have…

 

MOV ECX,6

MOV EAX,12

IMUL ECX,EAX

 

…then we would multiply 0x00000006 * 0x00000012 and get 0x0000006C in ECX.

 

Note that in either case, we could have left ECX undefined and said IMUL CX,AX,6 or IMUL ECX,EAX,6 and achieved the same net result.

 

As with multiplication, there are two forms, the unsigned DIV and the signed IDIV instructions.  Unlike with multiplication, the DIV instruction will always use EAX (and EDX) at some point, and the two instructions have the same behavior for any data size.  And if the divisor is zero, or the quotient is larger than the destination register, the processor will cough up a Divide Error and crash your game.  Depending on data size, we see the following effects:

 

  1. For byte-size numbers, DIV or IDIV [r/m8] the dividend is in AX, the divisor is [r/m8] and AX / [r/m8] à AH:AL, where the quotient goes to AL and the remainder goes to AH.  The rest of EAX is unchanged and EDX is not involved.

 

  1. For word-size numbers, DIV or IDIV [r/m16] the dividend is in DX:AX (high word in DX and low word in AX), the divisor is [r/m16] and DX:AX / [r/m16] à AX:DX, where the quotient goes to AX and the remainder goes to DX.  The rest of EAX and EDX is unchanged.

 

  1. For dword-size numbers, DIV or IDIV [r/m32] the dividend is in EDX:EAX (high word in EDX and low word in EAX), the divisor is [r/m32] and EDX:EAX / [r/m32] à EAX:EDX, where the quotient goes to EAX and the remainder goes to EDX.

 

When using the word and dword forms of DIV or IDIV, it is important to do a bit of housekeeping up front, so the instruction itself knows EDX and EAX are ready.  Most of the time these registers are used independently, so there is nothing which relates EDX as the upper half of a number and EAX as the lower half of the same number.  This is where CWD and CDQ come to play.  The CWD instruction (“convert word to dword”) takes the sign of AX and fills all bits in DX with the same value.  The CDQ instruction (“convert dword to qword”) takes the sign bit of EAX and fills all bits in EDX with the same value.  CWD is used very rarely in Diablo II, if at all, but CDQ is commonly seen right before a DIV or IDIV instruction.

 

Let’s see how this works.  Using either division instruction, let EAX = 0 and EDX = 0 before we start.  Now we execute…

 

MOV EAX,0x6E

MOV ECX,6

DIV CL

 

…which gets us 0x6C/0x06 or 0x12 with remainder 0x02.  So AL = 0x12 and AH = 0x02.  But if we execute…

 

MOV EAX,0x6E

MOV ECX,6

CWD

DIV CX

 

…then we will see 0x006C/0x0006 with remainder 0x0002.  So AX = 0x0012 and DX = 0x0002.  And finally, if we execute…

 

MOV EAX,0x6E

MOV ECX,6

CDQ

DIV ECX

 

…then we will see 0x0000006C/0x00000006 with remainder 0x00000002.  So EAX = 0x00000012 and EDX = 0x00000002.

 


These are most of the important math functions, so let’s look at the some logic functions.

 

The first we’ll look at is the AND instruction.  ANDing is a boolean operation that is extremely common in computers.  It is one of the most basic and most important operations that can be performed.  Here’s what an AND instruction looks like:

 

AND EAX, EDX

AND EAX, FF

AND BYTE PTR DS: [{location}], 3C

AND BX, WORD PTR DS:[{location}]

 

As you can see, it works with registers, memory locations or values, but you still need to know how it functions.  This is one of those important times when you need to know how to convert to binary because AND functions on a binary level.  AND goes through and compares every bit in a number with the corresponding bit from another number.  If either or both of these bits or 0, the AND results in a 0 bit.  If both are 1, the AND results in a 1 bit. 

 

Let’s see how AND works, using 243 and 149.  For simplicity, we will look at byte-size numbers rather than full dword numbers.

 

In binary, these numbers are: 1111 0011 and 1001 0101.  Now we go through and compare each bit.  If either is 0, the resulting bit is 0, but if both are 1, the resulting bit is 1.

 

1111 0011  (243)

1001 0101  (149)

----------------

1001 0001  (145) ßResult

 

With the assembly instruction AND, the result is placed in the first value.  So with our above examples, the result is placed in EAX both times.  This being the case, you CAN’T do something like:

 

AND 7F, EAX

AND 7F, FF

 

It won’t work.

 

Following the AND instruction is the OR instruction, which looks a lot like the AND.  You can use the OR instruction just like AND:

 

OR EAX, EDX

OR EAX, FF

OR BYTE PTR DS: [{location}], 3C

OR BX, WORD PTR DS:[{location}]

 

The result of an OR, however, is far different from an AND.  In an OR, we go through and compare each bit of a number, but if either is 1, the resulting bit is 1.  If both are 0, the resulting bit is 0.  Returning to our two sample numbers, 243 and 149, we already know the binary values are 1111 0011 and 1001 0101.  Let’s perform the OR.

 

1111 0011  (243)

1001 0101  (149)

----------------

1111 0111  (247) ßResult

 

You can see that the result is far different from the AND.  The result is placed in the first value of the assembly instruction, so the result would be placed in EAX in both of our above instructions.  Once again, you can’t do:

 

OR 7F, EAX

OR 7F, FF

 

Just like with AND, this doesn’t work.

 

XOR EAX, EDX

XOR EAX, FF

XOR BYTE PTR DS: [{location}], 3C

XOR BX, WORD PTR DS:[{location}]

 

An instruction closely related to OR is XOR, the exclusive OR.  Whereas an OR operation will return a 1 if either or both inputs are 1, the XOR operation will return 0 if both inputs are either 0 or 1, and it will return a 1 if both inputs are different from each other.  Using the same test numbers, let’s perform an XOR:

 

1111 0011  (243)

1001 0101  (149)

----------------

0110 0110  (102) ßResult

 

Since the XOR operation will return 0 if both inputs are the same, it is common to see an instruction like

 

XOR EAX,EAX

 

…in the code.  This is a very fast way to zero out a register, even faster than MOV EAX,0.  So optimizing compilers will use this form all over the map.

 

Once again, you can’t do:

 

XOR 7F, EAX

XOR 7F, FF


The next logic instruction of interest is the NOT instruction.  This one is simply:

 

NOT EAX

NOT DWORD PTR DS:[{location}]

 

It only works with one and only one register or memory location at a time.  Like AND and OR, this goes through and works on the binary level.  This time, it makes each bit the opposite of what it was.  If it was a 1, it becomes a 0.  If it was a 0, it becomes a 1.  Here’s an example:

 

NOT 243

 

1111 0011  (243)

----------------

0000 1100   (12) ßResult

 

The result is placed in the register or memory location used in the operation.

 

Some other logic operations we want to look at are the shift operations.  The first is the SHL instruction, which is shift left instruction.  This shifts the bits of the given number left a given amount.  It is the equivalent of a multiplication by a power of two, unless the number is so large (or the shift so many bits) that higher bits are shifted completely out of the number.  It looks like this:

 

SHL EAX, EDX

SHL EAX, 4

SHL DWORD PTR DS: [{location}], 13

 

As you can see, this one works with registers and numbers.  The first value must be a register because, like AND and OR, it is where the result is stored.  Here’s an example of how the shift works.

 

Shifting 243 Left 4

 

We remember the binary of 243: 1111 0011.  Now we add four 0s at the beginning.

 

1111 0011 à 1111 0011 0000

 

Then, we drop the left 4 bits (we are only working with byte-wide values now), which leaves us with 0011 0000.  Our value is 48 in decimal or 30 in hexadecimal.  If we had been shifting by 3, we would add three 0s at the beginning and drop the 3 left bits.  Like this:

 

1111 0011 000

1001 1000

 

That’s not so hard, is it?


Since there is a SHL instruction, there is obviously going to be a SHR instruction, which shifts bits to the right.  This is roughly equivalent to an unsigned division by a power of 2.  It looks like:

 

SHR EAX, EDX

SHR EAX, 4

SHR DWORD PTR DS: [{location}], 13

 

It works just like the SHL instruction, only we add 0s at the beginning and drop the right most bits.  For example:

 

Shifting 243 Right 4

 

1111 0011

0000 1111 0011

0000 1111

 

Shifting 243 Right 3

 

1111 0011

000 1111 0011

0001 1110

 

A very similar pair of shifting instructions is the SAL/SAR pair.  In actuality there is no difference between SAL and SHL, so you will not often find the SAL instruction in a listing.  There is a difference between SAR and SHR, however.   Whereas SHR is an unsigned shift to the right, SAR preserves the value of the sign bit.  It is more or less a signed division by a power of 2.

 

The final logic instruction is the NEG instruction.  This is actually useful as both a logic and a math instruction.  It stands for negate, and is the equivalent of the “change sign” key on your calculator.  The NEG instruction works on a register or memory location only:

 

NEG EAX

NEG DWORD PTR DS:[{location}]

 

This makes the value in EAX negative.  NEG is very similar to NOT, with NEG yielding a value one higher than NOT.

 

Negative numbers are very screwy on computers, so don’t be surprised with the numbers you get.  To see for yourself, start Windows Calculator and go into binary or hexadecimal mode.  Then subtract 2 from 1 and see what result you get.  Look at all the different data sizes when you do.  Rest assured, this is considered to be –1 as far as computers are concerned.  This is because for a number of a given data size, the most significant bit is used to carry the sign of the number.

 

All arithmetic and logical instructions can have effects on a special register called the Extended Flags Register (EFL).  This register is displayed in OllyDbg in the Registers section but it is actually a list of single bit values known as flags.  (We will talk more about Ollydbg soon enough.)  The eight most common flags are shown above EFL in the Ollydbg sample on the next page.

 

 

These eight flags are known as Carry, Parity, Auxiliary Carry, Zero, Sign, Trap, Direction and Overflow.  The most important flag we will use is the Zero flag.   The Sign flag is set by the value of the most significant bit in a number as described earlier, and can be used to determine if the number is positive or negative.

 

Now we’ll look at program flow instructions, starting with the CMP instruction.  CMP stands for compare, and is used to compare values and make decisions based on that.  You’ll never see a CMP instruction that isn’t followed closely (within a few lines of code) by some kind of jump.  A CMP followed by a jump is an equivalent of the IF..THEN statement in higher-level programming languages.  The CMP instruction looks like this (among many possible forms):

 

CMP EAX, EDX

CMP EAX, 30

CMP DWORD PTR DS:[{location}], ECX

CMP BL, BYTE  PTR DS:[{location}]

 

The CMP instruction is a hypothetical subtraction, that is, the second value is subtracted from the first but neither value is actually changed.  But the operation will set or clear the appropriate flag (s) as if the subtraction had occurred.  In the above example, the CMP instruction compares EAX to EDX, or EAX with the value 30, or the register values with values in memory.

 

Very similar to the CMP instruction is the TEST instruction.  The TEST instruction is a hypothetical logical AND, and as with CMP neither value is changed but the appropriate flag(s) will be set or cleared.  Unlike the CMP instruction, it does not matter which number is first and which number is second.  The TEST instruction looks like this:

 

TEST EAX, EAX

 

It’s no typo that we have EAX shown twice.  The TEST instruction is usually used in this way, to see if a register value is zero or non-zero.  You can use the TEST instruction in other ways:

 

TEST EAX, EDX

TEST EAX, 30

TEST WORD PTR DS:[{location}], CX

TEST BL, BYTE  PTR DS:[{location}]

 

The main reason for using these two instructions is they set flags as a result of conditions, but you don’t alter the conditions themselves in looking at them.


Most program flow handling has two parts, the decision and the branch.  If the CMP and TEST instructions are the program decisions, how do we execute program branches?  In assembly, there are unconditional and conditional jumps.  We will start with the unconditional jump, which is the JMP instruction.  A JMP instruction is ALWAYS executed.  The jump is always taken.  It looks like this:

 

JMP ADDRESS

 

ADDRESS is the location of code to jump to.  This value can be stored in memory, in a register, or given explicitly.  There are several forms of JMP accordingly:

 

JMP EAX

JMP 6FAC1FA0

JMP [6FAC1FA0]

 

The first form jumps to the location specified by EAX.  The second form jumps to the explicit address 6FAC1FA0.  The third form retrieves the destination address which is stored at 6FAC1FA0, rather than to 6FAC1FA0 itself.  And remember, any of these jumps is always executed.

 

There are times when we don’t always want to jump.  We may only want to jump when two numbers are the same or when one is greater than the other.  For this we have conditional jumps.  Many conditions are derived from the values of the various flags.  Below is a list of important conditional jumps we will frequently see.  Many jumps have alternate names, as programmers prefer to have names that reflect the context of the program branch.  Thus JE and JZ are the same thing, JNE and JNZ are also the same thing.  Context of one decision may be whether two numbers are equal, but context of another decision may be whether a result is zero.  Other alternates are indicated in brackets, but I will not address them further.  Note: OllyDbg does not retain alternate forms, so you would see JE when the context is clearly intended to be JZ.

 

JE – Jump If Equal

JNE – Jump If Not Equal (form not retained by OllyDbg)

JZ – Jump If Zero (form not retained by OllyDbg)

JNZ – Jump If Not Zero

JG [JNLE] – Jump If Greater Than (signed numbers)

JGE [JNL] – Jump If Greater Than Or Equal To (signed numbers)

JL [JNGE] – Jump If Less Than (signed numbers)

JLE [JNG] – Jump If Less Than Or Equal To (signed numbers)

JA [JNBE] – Jump If Above (unsigned numbers)

JNB [JAE] – Jump If Not Below (unsigned numbers)

JB [JNAE] – Jump If Below (unsigned numbers)

JBE [JNA] – Jump If Below Or Equal To (unsigned numbers)

JS – Jump If Signed

JNS – Jump If Not Signed

 

As you can see, we can make our jump based on a variety of numerical things.


Each of these conditional jumps works just like the JMP instruction:

 

JE ADDRESS

JNZ ADDRESS

JG ADDRESS

JGE ADDRESS

JL ADDRESS

JLE ADDRESS

JA ADDRESS

JBE ADDRESS

 

And just like the JMP instruction, the address can be given in any number of ways: from memory, directly, or through a register.  They’ll all work.  So now that you know what these conditional jumps do, you need to know how to use them.  Conditional jumps are always preceded by a CMP or TEST statement.  It is from this CMP or TEST statement that the processor decides if a jump should or should not be taken.  Thus program flow branches one way by executing the jump, and the other way by not executing the jump  Here is an example:

 

MOV EAX, 14

CMP EAX, 12

JGE ADDRESS

 

We know what the MOV instruction does, it moves the value 14 into EAX.  Now we call the CMP statement.  We’re comparing with the value 12.  Let’s look at the jump below it.  This is a JGE jump, so we jump if the CMP instruction was greater than or equal to.  Is 14 greater than or equal to 12?  It’s greater than, so we take the jump.  Here is another example:

 

MOV EAX, 14

MOV EDX, 32

CMP EAX, EDX

JGE ADDRESS

 

This time, we’re moving 14 into EAX and 32 into EDX.  Once more, we do a CMP instruction, this time between EAX and EDX.  Our jump is a JGE instruction again, so we need to decide if 14 is greater than or equal to 32.  This time, it’s not, so we don’t take the jump.  If we switched the code to:

 

MOV EAX, 14

MOV EDX, 32

CMP EAX, EDX

JL ADDRESS

 

…we would take the jump.  Why?  Because we are now checking to see if 14 is less than 32.  Since it is, we jump.

 

A close analogy for CMP or TEST followed by a jump is the IF..GOTO statement in BASIC.


Now let’s look at the CALL instruction.  CALL is a lot like JMP, except instead of branching to other code, you are executing an inner procedure (a.k.a. subroutine).  Every CALL is always unconditional, so you do not need CMP or TEST statements.  A call looks like this…

 

CALL ADDRESS

 

…and works similar to JMP.  The address can be stored in just about anything, and the CALL is always made.  The difference is that a CALL statement will save the address of the next instruction on the stack, and use that saved address to return to that next instruction when the inner procedure has completed its task.  So if we had:

 

CALL ADDRESS

ADD EAX, 4

MOV EDX, EAX

 

all the instructions would execute.  First, we would jump to the code at the CALL instruction’s address.  Once we finished with it, though, we would jump back to the ADD EAX, 4 instruction and do that.  Then we’d do the MOV EDX, EAX instruction.  If this were a jump, we would go to the address, but we wouldn’t come back to execute the rest of the code.

 

In order for a CALL statement to know when to return to the address right after where it started, it needs a RETN, or return, instruction.  The RETN must always be in the program flow of the section of code executed by A CALL, or you will crash and burn hard.  RETN has two forms:

 

RETN

RETN {number}

 

Both will take the code back to right after the CALL statement was made.  The difference is what happens on the stack when the return occurs.  A RETN by itself says, this inner procedure doesn’t have any parameters.  A RETN with a number (always a multiple of 4) says, this inner procedure does have parameters, and the number of parameters is the number after the RETN divided by 4.  That is because a CALL works a lot like a PUSH, and a RETN works a lot like a POP, and these instructions handle DWORD values exclusively.  We call this cleaning up the stack, as parameters are pushed onto the stack ahead of a call.

 

Parameters work like this:

 

PUSH [Arg2]

PUSH [Arg1]

CALL Inner_function_address

TEST EAX,EAX

 

Inner_function:

MOV EAX,DWORD PTR [ESP+4]                ; Arg1

MOV ECX,DWORD PTR [ESP+8]                ; Arg2

{do something and return a value in EAX}

RETN 8

 

Let’s analyze what is happening here.  Parameters are PUSHed onto the stack in reverse order.  So if the stack pointer [ESP] is pointing to address 0012F000 when we begin, after the call is executed we will have this stack:

 

0012EFF4:       {return address}           ß ESP points here now

0012EFF8:       Arg1

0012EFFC:      Arg2

 

Now after the inner function is complete, the RETN looks at 0012EFF8 and gets the address of the instruction immediately following the CALL, which in this case is TEST EAX,EAX.  But we need ESP to point to 0012F000, and a RETN by itself only moves the stack pointer by 4.  So the value 8 after the RETN says, move the stack pointer up an additional 8 bytes, and we have returned to where we began at last.

 

Here is an Ollydbg example of a parameter-based CALL in Diablo II from d2game.dll:

 

 

In this example, there are two PUSHes ahead of the CALL.  They do not need to follow each other immediately, the stack takes care of their association with the CALL.  If there are a lot of steps in between, you may have to work at keeping track of what belongs with what.  It is especially confusing if there is a CALL in front of the call that actually receives the parameters you are following!  We will discuss CALLs in Diablo II in much more detail later on.

 

The next instruction we’re going to look at is the NOP instruction.  This stands for no operation, and does exactly that; nothing.  The instruction looks like this:

 

NOP

 

Amazing, huh?  NOP statements can be very useful when we need extra code space to fit our own modifications in.  They’re also very useful when we need to remove/nullify a section of code.

 

When pointing to memory, we do not have to explicitly state the location.  Our pointer references can be simple formulas themselves:

 

DWORD PTR DS:[EAX]

DWORD PTR DS:[EAX + 6C]

DWORD PTR DS:[EAX + 4* EDX]

DWORD PTR DS:[EAX + EDX - 40]

 

A common use of the last form is to reference data from a table.  One register might contain the base address of the entire table, another register might contain the record size times an index number (called the record offset), and the constant would be the number of bytes to a particular field.  We will see a lot of this form, and discuss it in detail later.
The last instruction I will mention is the LEA instruction.  This one can confuse even experienced coders, so be aware of its peculiarities when you look at Diablo II code.  LEA stands for Load Effective Address, and it is a way of calculating memory locations directly, using formula-based pointer references as shown above.  But due to a peculiarity of Intel processors, it can also be used for making very fast specific multiplications.  So when you see instructions like

 

LEA EAX,DWORD PTR DS:[EAX+EAX*4]

LEA EAX,DWORD PTR DS:[EAX+ECX*8+4]

 

you could very likely be looking at the equivalent of “EAX = EAX * 5” or “EAX = ECX * 8 + 4”.  The standard form is [register1 + register2 * number1 + number2] where register1 may not exist at all, register2 can be the same as register1, number1 is 2, 4 or 8 (or there is no multiplier shown for register2 and it is implicitly 1) and number2 can be any value or not present.  The reason these show up so often in the code is because you cannot get a faster answer when you want to multiply by 3, 5 or 9, than to use this instruction.  And the second form shown is very fast for locating data in tables with very small record sizes (1, 2, 4 or 8 bytes), where number2 is the field offset.

 

While this hasn’t been a full, in-depth explanation on assembly, it should be enough to get you started.  You may find that some of the discussion about instructions is simplified if you study assembly language further, but that is because it takes too much detail and time to go into exactly how everything works.  What you have read here is enough to begin navigating through Diablo II code.  If you are interested in learning more assembly, search the Web for tutorials and examples.  There are many flavors of assembly language, one for each processor family in fact, so be sure to specify Intel, Pentium or P4 in your search.

 

One resource worth mentioning specifically is the Intel IA-32 Architectures Software Developer's Manuals (isn’t that a mouthful?).  Thes manuals will tell you more than you ever wanted to know about the processors, the complete assembly language instruction set, and how it all works together.  What they will NOT do is teach you how to make a program out of them.  I mention the Pentium4 manuals here, but the basic instructions haven’t changed since the first 32-bit processor made its debut.

 

Intel’s Assembly Guides (get Volumes 2A and 2B)

http://developer.intel.com/products/processor/manuals/index.htm

 

If you want to know more about how the Intel processor behaves internally, Volumes 1 and 3 can be worth reading.  But they don’t explain any assembly instructions.

 


The OllyDbg Assembler/Dissassembler

 

Now that we know some basic assembly, lets take a look at the assembly code inside one of the Diablo II DLLs.  To do this, we’re going to use OllyDbg.  This program has a number of features, and the Assembler/Dissassembler is just one of them.  Because of this, I’ll be showing you the OllyDbg interface a bit at a time rather than all at once like with XVI32.  So let’s start OllyDbg and see what we have:

 

 

You should see something similar to this.  Depending on what options you may have set, OllyDbg might begin with a number of blank windows in the gray area, we will ignore them for now.

 

Let’s take care of one detail that will make reading code much easier for us: window appearance.  Ollydbg “out of the box” shows black text on a yellow background.  But we want to readily distinguish parts of an instruction, or one type of instruction from another, so we will select one of the prepackaged color schemes.


Start by clicking on Options and choosing Appearance:

 

 

Now pick the Defaults tab.  Go to Default syntax highlighting and choose Christmas tree from the dropdown list.  Further customization is possible with the Code Highlighting tab, but we won’t discuss it in this tutorial.

 

 

Code samples included in this tutorial come from a slightly modified Christmas Tree appearance.  Whether or not your screen colors look a bit different than the ones here, the content is what matters.  So let’s go on to said content, and for that we need to see some code.

 

To access the Assembler/Dissassembler, you want to go to “View” then go towards the bottom and select “File”.

 

 

This will bring up a familiar dialog box, which you can use to open a file.  Let’s open up D2Common.dll (v1.11b) and get a look at that.

 

 

This certainly doesn’t look like assembly.  In fact, it looks a lot like what we saw in XVI32.  This is completely true.  OllyDbg is also a hex editor (I told you it could do a lot of things).  There are a number of ways to view a file, and hex is one of them.  Assembly code is another.  So let’s learn how to change views.  Right click, and you’ll get following menu:

 

 

I won’t go through the entire menu, but these are the important ones.  “Search for” will allow you to search for a binary string, an assembly command, an address, or some other things.  It depends on what view mode you are in.  “Save file” will allow you to save the file.  “Go to offset” is just like the “Goto…” option in XVI32.  It will bring up a window where you enter the offset to go to and it takes you there.  Below those are the view modes.  “Hex” will allow you to choose from a few different hex editor displays, including the one we’re in.  “Text” will allow you to display only strings, which is the data to the right.  You’ll notice the “This program cannot be run in DOS mode.” string again.  “Short” doesn’t really matter, and you’ll probably never use it.  “Long” has the very useful “Address” display mode, which I’ll explain later.  Next is “Float”, which we can ignore just like “Short”.  Then we get to “Disassemble”, which is what we really want to do.  After that is “Special”, which contains the option to view PE header information.  So let’s choose “Disassemble” and see what we get.  We will call this method of disassembly “file disassembly mode.”  The meaning will become clearer later.

 

 

You might recognize the DEC instruction, the POP instruction, the ADD instructions, and even a MOV and a NOP instruction in there.  However, there’s that strange “????” and all of the ADD instructions are a little odd looking.  This is because what we’re viewing isn’t actually meant for assembly.  There are lots of things a DLL file, and assembly code is just one of them.  What we’re seeing is called a PE header, and if you want to get a better look at it you should go to “Special” view and choose the “PE Header” option.  We don’t really care about that, though.  Let’s head to some code.  Go to offset 72E67.  You should see this:

 

 

Now this looks more like assembly.  You should be able to recognize almost all of the instructions.  This particular sequence of code is that part that determines the gamble cost for an item, that we referred to in the discussion about hex editors.  The highlighted line is comparing the value of register EAX to a constant, but in this particular application the constant is a reverse ASCII representation of the three-letter code for rings (always padded with a space).  Why reverse?  Remember from our discussion of hex editing with XVI32 that Intel designed their processors to read words and dwords in “little-endian” format, where the least significant byte is at the left and the most significant byte is at the right.  So the equivalent string is “ nir” instead of “rin “.

 

Look down at line 72E74, this is the actual code we changed when we were exploring with XVI32.  In the second column we see the same six bytes we looked for with the hex editor.  In the third column we see the equivalent assembly language instruction, JE 00072FF0.  We recognize this is a Jump if Equal.  So this code says, if I have a ring then I jump to the target offset 00072FF0.  (In file disassembly mode all targets are file offsets.)  Look past this jump and you will see a similar test for amulets.

 

Now we want to make all items use the Gamble Cost field just like rings and amulets.  We want to make the conditional jump into an unconditional jump.  So we need to assemble the new instruction.  Our new code should look like this:

 

JMP 00072FF0

 

To change this, make sure you have the JE 00072FF0 line highlighted in gray.  Then right click and choose “Assemble” from the popup menu.

 

 

This brings up OllyDbg’s assembler screen, where you can enter in the command you want to do.  You’ll notice it starts with the command we have highlighted.

 

 

Just edit the JE to JMP and hit the “Assemble” button, and the code will change.

 

 

As you can see, the assembler screen stays open, but our code has indeed changed.  When you’re finished with the assembler screen, just hit “Cancel” and it will close.

 

 

What about that NOP line which is currently highlighted?  Where did that come from?  OllyDbg is smart about assembling instructions and clears any excess bytes with one or more NOP instructions.  The conditional jump we replaced uses six bytes, but the unconditional jump uses only five bytes.  (This is why the hex edit exercise had the ‘90’ byte at the end, it is the extra byte.)

 

You can get into trouble if you aren’t careful.  OllyDbg does not ask if you want to overwrite bytes in the next instruction.  So if you had been changing an unconditional jump to a conditional jump, you would have stolen the first byte from the next instruction (which we didn’t want to change), and OllyDbg would have cheerfully changed the rest of the bytes to NOPs.  But do not fear, there is an Undo Selection command on the right-click menu!  Select the instructions you need to undo and use the command to reset to the last saved coding.

 

If you want to save this change, right click to get the popup menu and choose Save File.

 

 

 

We’ve now changed the conditional jump to an unconditional jump, and now all items will use the Gamble Cost field just like rings and amulets originally did.  It was as simple as that.  Of course, this is the most basic of assembly changes, and we did it more to introduce you to OllyDbg than to learn complicated assembly.

 


The OllyDbg Debugger

 

Although we can use OllyDbg for assembly and disassembly, we will use it a whole lot more for it’s debugging capabilities.  The debugger potion of OllyDbg will allow us to follow the lines of code in the Diablo II program as they are executed.  It will show us the values in all of the registers and stack as well.  This is how you can track down what locations of code do what.  Before we can go code hunting, though, we need to understand how the debugger works.  We start by opening up OllyDbg once again:

 

 

In debugger mode, it is best to make OllyDbg run in full screen.  There is a lot to display, and the full screen keeps the workspace from getting cramped.  So click on the Maximize button and let’s get to work.

 

Alt-Tab to Windows and start Diablo II.  You should start Diablo II in windowed mode because you’ll be switching between it and OllyDbg quite a bit.  You can do this by adding “-w” to the command line.  When the game comes up in the reduced window,  start a single player game and wait for the character to appear in the current town.  This is necessary, because the three DLLs we are most interested in do not load that the very beginning.  They are loaded only when you first enter a game.


Once Diablo II is running, we need to attach OllyDbg to it.  We do this using the “Attach” option in the “File” menu.

 

 

This brings up a list of the currently running processes on your computer.  Depending on how many Windows components and other programs you have running, this can be quite a list, but the one we’re interested in is Diablo II.

 

 

If you are running Diablo II v11.0 or later, you will see a popup like the one below.  Click OK and ignore it.

 

 

Next you should see a single window with many different sections.  We’ll start by examining this window, then we’ll open a few others and look at those ones, too.  The window is called the CPU Window, and you can see a big C in the upper left.

 

 

As you can see, this window displays almost everything we talked about in the Basic Assembly section.  First, we have the program’s code.  We can follow this step by step as the program executes if we want.  I’ll explain how later.  We can also see the values of the program’s registers.  You should recognize each of them from the Basic Assembly section.  We can also see the program’s stack.  The “top” of the stack is highlighted.  I say “top” because you may notice that there are certain pieces of code that can change the location of the top of the stack.  Finally, we see a dump of some of the program’s memory.  The first part is the address/location and the second part is the value.

 

Now let’s open up another important window, the executable modules window.  This window can be opened by going to the “View” menu and choosing “Executable Modules”.  But the easy way is to select the E button on the toolbar:

 

 

When you attach a debugger to a process, the debugger will keep track of every program module that is required to maintain the process.  This will consist of the program executable, its DLLs, active Windows components, and monitoring components from guardian programs such as anti-virus and anti-spyware.  You can sort this table by clicking on the column header, so let’s group all Diablo II modules by clicking on the Path column header.  The example below is for D2 v1.11b.

 

 

You may remember at the beginning of this tutorial there is a list of DLL versions.  Here you can find the three DLLs from that table: D2Client.dll, D2Common.dll and D2Game.dll.  The last column in that table is labeled “Image Base” and has some hex numbers.  Now look at the leftmost column on this screen labeled “Base”.   Let’s compare the numbers from this screen (middle column below) to the Image Base values (right column below).

 

D2Client.dll      6FAB0000    6FAB0000

D2Common.dll      6FD50000    6FD50000

D2Game.dll        04900000    6FC20000

 

D2Client and D2Common match, but D2Game has a different base than expected!  This is known as a relocation.  Windows will move around any module that is too big to fit in available space when attempting to load at the image base.  This is the major reason why code you examine in the debugger may not quite match code you may read about in the Code Editing Forum.  Relocated DLLs do not always end up in the same place!.

 

Next, we’re going to look at the breakpoints window.  This one is opened by going to “View” and choosing “Breakpoints”, or by clicking on the B toolbar button.  It looks like this:

 

 

It starts empty, but we’ll be using it a lot later, so make sure you have it open.  The information is broken up into columns, but it doesn’t make much sense until we have a breakpoint to use as an example.  So let’s just skip that for later.  The final window is the run trace window.  Like the others, this is under “View” and the menu choice is called “Run Trace”.  Or you can hit the ellipsis button (three periods) on the toolbar.

 

 

Like the breakpoint window, this one doesn’t have anything at the moment.  We’ll be using it a lot later, though, so we should have it open.

 

You may have noticed that the code in the window with the assembly isn’t moving.  You may also have noticed that if you tried to click on Diablo II, it appears to be frozen.  This is because the program is currently paused.  You can tell if the program is paused by looking at the status bar in the bottom right.  You’ll notice it says “Paused” in nice red letters, as shown below.

 


To start the program we can use the “Debug” menu.

 

 

As you can see, I’ve circled a number of options.  This is because each of these options is a way to run the program and they all run the program differently.  Let’s go through them, starting with “Run”. 

 

If you choose “Run” the program will begin running again and OllyDbg WON’T run along with it.  This means it won’t follow code in the assembly window or change the registers in the register window or any of that.  This is how you’ll usually be running Diablo II because we don’t want to know each and everything that Diablo II does.  Doing so slows the program down an unbelievable amount and usually results in the program terminating unexpectedly.

 

The next two options, “Step into” and “Step over” are very similar.  They both execute and follow the current line of code and then pause again.  They’re good if you want to examine the changes in the stack or the registers very carefully.  The difference is when it comes to CALL statements.  Remember that CALL statements take you to another part of code and execute it.  If you choose “Step into” you’ll go to the first line of that new code and stop there.  This is why it is called “Step into”, because you go into the call.  “Step over” therefore steps over the CALL.  It doesn’t really go over it, but instead it executes all of the statements in the CALL before stopping.  Then it stops on the statement right after the CALL.  This is good if you want to skip over a pointless or long CALL statement.

 

The two options after this are “Animate into” and “Animate over”.  The difference between these two is the same as the difference between “Step into” and “Step over”.  One goes into the CALL and the other goes over it.  These both differ from the step choices because they continue to execute and follow code non-stop until they reach a breakpoint or the program closes.

 

The final choice is “Execute till return”.  This is useful if you want to see into the code of a CALL, but not follow it through for every step.  This will execute and follow code until it reaches a RETN instruction for the current procedure.  It will also stop when the program closes or it reaches a breakpoint.

 

Of all the options, you’ll be using “Run”, “Animate into”, and “Animate over” the most often.  Your question now is probably, “If ‘Run’ doesn’t follow the code, then how do I get OllyDbg to stop at a particular spot?”.  Remember that you’ll be using “Run” to run Diablo II most of the time otherwise it will go to slow for you to do anything and it will probably crash.  The answer to the question is to use breakpoints.

 

As the name implies, breakpoints cause the program to pause (break) when it comes across the breakpoint code.  There are several types of breakpoints in Ollydbg.  The three we will use the most are unconditional breakpoints, conditional breakpoints, and memory breakpoints.

 

  1. An unconditional breakpoint will stop the program in its tracks whenever the breakpoint is encountered.  This is the simplest and most frequently used of the three.  Pick an instruction in the disassembly pane and add a breakpoint.

 

  1. A conditional breakpoint is much like an unconditional breakpoint.  But you can also specify what condition must be satisfied before the debugger will stop the program when this breakpoint is encountered.

 

  1. A memory breakpoint is a different breed altogether.  This is used to find out whether a particular location is accessed, or written to.  While the other breakpoints are applied to code in the disassembly pane, the memory breakpoint is applied to data in the dump pane at lower left.  It is used to see what code accesses, or writes to, a memory location—very useful if you know the data but don’t know what function may reference it.

 

We will return to the other types of breakpoints later.  But for now we will work with unconditional breakpoints.

 

Let’s begin by setting a breakpoint at the same spot we changed when we forced all items to use the Gamble Cost field in the txt files.  Your first impulse might be to try to go to offset 72E74 like we did before.  If you try to, though, it won’t work.  This is because the offsets are different when the program is running.  When the program is running there are a lot of files that need to be loaded and a lot of them are going to have the same offsets.  Also, depending on how the module is developed, you may start actual code in the file at offset 1000h or at offset 0400h.  (Remember the first part of any module is the PE Header we mentioned earlier.)  Regardless of which file offset is the start of code, once the module is loaded into memory the memory offset to start of code is always 1000h.  So that file offset of 72E74 will become memory address 6FDC3A74.


Look at the code at that memory address.  Since we want to look at code in D2Common.dll, we first select that module from the Executables table (E button on the toolbar).  You will see the first line of executable code at the top, as shown below.  Note we are at address 6FD51000, which is the base address plus 1000h as said before.  Right-click somewhere on the code panel and choose the option Go To and then Expression:

 

 

You will see the dialog box below.  Enter the target address and click OK.

 

 

Now you should see the code below.  Let’s scroll up a few lines so we can see the test for rings.

 

 

The code may look familiar from before.  It should, it’s the same code.  But now notice the jump instruction itself looks different.  Instead of JE 00072FF0 you now have JE 6FDC3BF0.  OllyDbg automatically fills in the target memory address when in debug mode, rather than using the file offset address as when in file assembly mode.  This is a very powerful aid, as now you see how you move around in a program exactly as the program itself does when running.  Now let’s make the change we made before, so we choose the assembly command and see

 

 

Note the target memory address is carried into the dialog box.  But all we want to do is, as before, change the JE to JMP:

 

 

Now click the Assemble button, and you will see your change in the code window.  The code you changed appears in red:

 

 

Unlike with file assembly, you have changed the program in memory, and as soon as you check in with your gambling vendor you will see new prices which are not calculated from your character level.  Changing code in debug mode is interactive and immediately effective!  Go ahead and run the game (F9 key, or Debug menu Run option).  Check out the gamble prices and you should see the same values for the same base items, regardless of what your character level is.  For example, ring mail has 20177 in Armor.txt Gamble Cost, so the vendor better be offering it for 20177.

Enough of that, though.  We want to place a breakpoint and see how they work.  You can do this by highlighting a line of code and either right clicking and selecting “Breakpoint” and then “Toggle” or you can just press F2.  This is the same as using the menu.  I recommend you get used to pressing F2 because you’ll use a lot of breakpoints.

 

 

Now that our breakpoint is set, we have two indications of this.  Our line of code has a red mark to the very left and the breakpoint has appeared in the breakpoints window.

 

 

 

 

Now we can take a better look at the breakpoints window, since we have a breakpoint in it.  You can see that it shows the location of the breakpoint and also what executable module it is in.  Next you see the column called “Active”.  This says “Always” because our breakpoint will always stop the program.  Finally, it shows the code where we set the breakpoint:  JE 6FDC3BF0.

 

So let’s see how the breakpoint works.  Go to your gambling vendor and choose the Gamble task.  The second you do, it seems like Diablo II has frozen.  Well, that isn’t altogether incorrect.  It has paused because game execution hit the breakpoint we set.  In fact, you will probably be kicked directly into the OllyDbg screen with the instruction pointer resting on your breakpoint.  The red bar over the address has changed to a black bar with red letters and the complete line will be highlighted.

 

 

Let’s take a close look at this line.  Since it is a jump instruction, there will be a line drawn between the jump itself and the target address.  If the line is red, the jump will occur and the next instruction executed will be the one at that target.  If the line is gray, the jump will not occur, and the next line down will be the next line executed.  JMP instructions are unconditional, so they will always show up as red lines.  But if we had left the original instruction alone, and we looked at anything other than a ring, this line would be gray.

 

You will now be able to look at the stack and the registers and see what their values are.  If you want, you can use the “Step into” and “Step over” choices a few times just to get the hang of how it works.  You’ll notice that each time a register changes it becomes red.  This makes it fairly easy to identify which registers are probably being used for whatever function we’re doing.  Some will change permanently while others will end up back like they were at the start of the function because they’re PUSHed onto the stack at the beginning and POPped from the stack at the end.  When you’re sick of playing around with the “Step into” and “Step over” stuff, choose the “Run” option again and go back to Diablo II.  The game is now running like normal again and the gamble price appears for the item.

 

Let’s not worry about saving this change yet.  For our next topic, we want to change the code back to a conditional jump, so repeat the previous steps and edit JMP to JE in the Assembly dialog.  You will see the NOP disappear, as we have gone from five bytes to six bytes needed for the jump instruction.


Now we’re going to look at using run traces, so set a breakpoint at address 6FDC3A67.  This is where the item is checked to see if it is a ring.  Once more the program freezes and we can go to OllyDbg to see it at our breakpoint.  Before you do anything, though, go to the “Debug” menu and choose “Open or clear run trace”.

 

 

You’ll notice that something has appeared in the run trace window.

 

 

 

This is the line of code we’re currently stopped at.  With run trace, each time a line of code is executed it will be added to the run trace window.  This will let us go back and look at all the code executed between two breakpoints or in some function.  If you like, you use “Step into” and “Step over” to see what happens.  When you get sick of that, you can choose the “Execute till return” option.  Once you do, you’ll notice that the run trace window fills up with lines of code and that you’re stopped at a RETN statement.

 

 

Here we are testing an item with a certain three letter code against a ring.  The zero flag is not set in line 6FDC3A67, so when we get to the JE 6FDC3BF0 at 6FDC3A74 we ignore the jump and continue to the next compare.  This time the item is compared to an amulet code, and the zero flag is set, so the item is an amulet.  The JE 6FDC3BF0 at 6FDC3A7F is executed, and the next line in the trace is the instruction at 6FDC3BF0.  There EAX is loaded with a value (F618h = 63000), we restore a bunch of registers that were previously saved, and we RETN to the code which called this procedure.

 

What about that ADD ESP,14?  Didn’t we say we should not mess with the stack pointer directly earlier?  When starting and closing a procedure, we often want to declare local variables.  In assembly language this is done on the stack.  We figure out how much space we need for all variables (including structures and arrays), then manipulate the stack pointer to make and clear the space.  If you scroll up above the breakpoint, you would see the first line of the procedure is SUB ESP,14.

 

 

The SUB ESP,14 instruction allocates local variable space, the ADD ESP,14 instruction clears it.  If you use this convention, you must balance the stack arithmetic from beginning to end, or you will foul up your return address.  It would be similar to what happens if you push parameters ahead of a CALL without using the RETN # form.

 

The advantage of using “Execute till return” is that it’s fast and we don’t have to have a second breakpoint.  The run trace window now has a list of instructions from our breakpoint to the RETN instruction.  Just as an important note, the most recent instruction is actually at the very bottom and the first instruction traced is at the very top.  You can also use “Animate into” and “Animate over” to do a run trace.  Just make sure that you have two breakpoints, one to start at and one to end at.  If you don’t, those will both go on indefinitely and probably result in a crash.

 

Let’s look at some more game code.  Earlier, we discussed using PUSH and the stack to pass parameters to a procedure.  This is sometimes known as the STDCALL convention.  There is another convention, FASTCALL, which also uses two registers to pass the first two parameters, with all others PUSHed as before.  ECX gets the first parameter and EDX gets the second parameter.  Here is an example:

 

 

This CALL in d2game.dll has three parameters.  But since it is a FASTCALL procedure, only the third parameter is PUSHed.

 

To navigate through a CALL when you are just browsing code, you can highlight the CALL line and hit the Enter key.  To back up when browsing, hit the subtract key and the previous line you highlighted will be restored.  Let’s drop into the procedure called in the previous example.

 

 

Since this is the start of a procedure, note that ECX provides information without itself being defined in the procedure.  If this was a pushed parameter, it should look like the Inner_procedure example presented when we first discussed the CALL instruction.  Often these uses of ECX and EDX in the beginning of a procedure are our only clue that this procedure uses the FASTCALL convention.

 

There is something else odd in this code sample.  We discussed the CALL instruction as using a register or address to get the first instruction of an inner procedure.  So what is this <JMP.&D2Common.#10800> about?  While many CALLs are used to jump around in a module, they can also be used to jump between modules.  Here we are in D2Game.dll, and the procedure we want to use is in D2Common.dll.  When the game was developed, Blizzard declared some procedures in each module as exports, that is, they could be readily used by another module’s code.  Exports are referred to by function name, or by ordinal number if no export name has been defined.  The using module is said to import the function from the other module.  When the game is loaded, Windows handles the work required to marry such calls in an importing module to those procedures exported by the other module.  As far as we are concerned, there is no difference in how OllyDbg steps into or traces through an imported procedure or a local procedure.
The last feature I will discuss is code analysis.  OllyDbg can make some very educated guesses about program structure from its disassembly.  The tool we use to display them is the Analysis option in the context menu.  Right-click in the code window, and near the bottom you will find the Analysis commands:

 

 

Previously we have looked at raw debugger code.  Here is what analyzed code looks like:

 

 

Now you can see the procedure itself is bracketed at left, and an inner loop has been identified between 049A4CA0 and 049A4CAD.  Jump directions are indicated at left with upward and downward carets, and jump targets are shown with the > symbol.  This is just the beginning of the information OllyDbg’s analysis feature can produce!


Our goal for this next discussion is to find the section of code that displays DIFFICULTY: HELL and DIFFICULTY: NIGHTMARE when you have the upper right text activated in the main game window.  Blizzard left out the DIFFICULTY: NORMAL and we aim to put it in the game.

 

We mentioned other types of breakpoints earlier.  Let’s look at a conditional breakpoint.  There is a procedure for retrieving strings that are loaded from tbl files when passed an index number in ECX.  This procedure is D2Lang.#10000; it is used in many places in D2Client.dll to build user interface elements.  But first we need to calculate the index number we want to locate.  For this we will use AFJ’s Tbl Edit and look at String.tbl.  We find the three strings starting with Hell:

 

 

When loaded in the game, these three files are assigned base index values.  String.tbl has a base index of 0, PatchString.tbl has a base index of 10,000 and ExpansionString.tbl has a base index of 20,000.  These are decimal numbers, so their hex equivalents are 0x0000, 0x2710 and 0x4E20 respectively.  Every string loaded in the game is identified by the index number shown in Tbl Edit added to the base index, either hex or decimal form but hex is what the code shows.  Here we see the first difficulty string is 0x1422 and with no adjustment that is what we will use.

 

Now let’s see how this index is used.  We will start by identifying the line that actually switches program execution from D2Client.dll to D2Lang.dll.  We right-click on the disassembler pane background and select Search for | All intermodular calls.

 

 

This brings up the Found Intermodular Calls window.  Click on the Destination field header, and all the imported functions will be sorted, including those referenced by ordinal only.  Scroll down until you find any reference to D2Lang.#10000.

 

 

Once you find a reference, highlight it and hit the Enter key.

 

 

This jumps you to one call, but it is not likely to be the right one.  So we will hit enter again and be directed to a very special line in a block of similar lines.

 

 

This is the jump table entry for D2Lang.#10000.  Wherever the function is called as we found above, when executed the game will step through the jump table entry.  So we will put a breakpoint here.  But if we put an unconditional breakpoint here we will be stopped all the time before we can even think of finding the right reference.  So we will use the index number of our desired string and set a conditional breakpoint.  Right-click and find the conditional breakpoint command:

 

 

Then set the condition.  The dialog box (see next page) takes a C-style condition, so we will enter ECX==1422 into it.

 

 

Note this breakpoint shows as a light purple instead of red when set.  Now we run the program and wait for the breakpoint to be triggered.  You need a character that can play a game higher than Normal in order to trigger the string reference.  This happens very quickly, but when you look at the disassembly pane the difference is very subtle.  Instead of black on light purple, you see red on light purple when the breakpoint is actually encountered with the right condition.

 

 

Well, this doesn’t tell us much about the code that called the imported function.  So we will look at the stack pane because that is where we will find the return from this special call.  Click on the highlighted line and it will turn red:

 

 

This says we should look at D2Client.6FAFFF25.  Hit the Enter key and you will go there in the disassembly pane.  It will be the first line after the CALL you just executed when the breakpoint triggered.  So scroll up a bit and you will see the actual call:

 

 

That is all fine and well, but we aren’t really interested in the call itself.  What we want to see is the logic that set ECX to 0x1422 so it would display difficulty: hell.  So scroll up some more.

 

 

Here we see the whole front end of this display procedure.  Right above the call to the string retrieval function we see

 

MOV ECX,EBX

 

Above that we see two lines of interest:

 

MOV EBX, 1422

MOV EBX, 1423

 

We know from AFJ Tbl Edit that the first reference is Hell and the second Nightmare.  String 1424 happens to be Normal.  But there is no equivalent line for referencing the normal string in this section of code.

 

Examine the code logic.  The procedure starts by retrieving a byte from a data location in D2Client.dll and expanding it to fill EAX with a MOVZX instruction.  This happens to be the game difficulty.  Then if the difficulty is 1 or 2 (Nightmare or Hell) the corresponding string is loaded.  But if the difficulty is 0 (Normal), we would jump to 6FAFFFC0, which is at the end of this procedure.  We want to change the logic so we load a string for any difficulty.  Note that the difficulty is decremented a couple of times and used only for testing.  If we let it be decremented only once, and then negated, we could add the difficulty to the middle string and get the correct string as a result:

 

MOV EBX,1423

NEG EAX

ADD EBX,EAX

JMP SHORT 6FAFFF04

 

Now let’s edit the code in Ollydbg, as shown on the next page.

 

 

Switching back to the game, we see it still works for the higher difficulty.  Now let’s load a Normal game and look.  It is successful!

 

 

We talked about code replacement and how to navigate through it.  But what about new code, or code that is just a bit larger than what we might have room for?  New code is beyond the scope of this tutorial.  But there are times when we need a little more room for something simple.  One important case is the most requested code edit in the entire game: changing the number of skill points your character receives with each level gained.

Let’s look at the existing code:

 

 

This section generates the 5 stat points and 1 skill point in unmodded Diablo II.  I have commented each line so you can see what is happening.  Here we have an imported function, D2Common.#10627, whose purpose we know: add to stat.  The parameters are the four PUSHes ahead of the CALL:

 

Arg4: Param (for stats that have parameters of their own, like oskills)

Arg3: value to increase the stat by

Arg2: the ID of the stat from ItemStatCost.txt

Arg1: handle for a character’s essential data structure, called ptUnit

 

The first call gives you the stat points when you level up.  Since there is a field in Charstats.txt that allows you to determine stat points per level, so no code editing is required here.  (Unless you are modding v1.09x and earlier, the field was added with v1.10.)   The second call gives the skill points, but there is no field lookup here, so we have to roll our own solution.  The required code in ASM is

 

MOV EAX,{number} ß amount to gain per level

IMUL EAX,EBX

 

And we need to feed the second push our skills/lvl * levels gained product.  But all four arguments use only 6 bytes and the new instructions need another 8 bytes.  There is no room for all of that!  You cannot insert extra bytes here, you have to find some room and jump to it.

 

In every DLL there is a little unused space at the end of the code segment.  To find it, go to the code window and scroll all the way to the bottom of the disassembly.  You will see a bunch of lines that will read ADD BYTE PTR [EAX],AL (analysis off) or DB 00 (analysis on); both forms are a 00 coding.  Highlight the last line, then hit the Page Up key several times until you see some other instructions.  The space below these other instructions is called slack space, and we can use it.  Make note of the first address which has the 00 coding.  In the example below, that is 04A67044.

 

 

Now return to the skill points code.  We need five bytes to form a JMP that reaches slack space.  So we will overwrite the first three PUSH instructions with a JMP:

 

 

Which gives us

 

 

You can see I left the fourth PUSH alone.  We didn’t need its byte.  Now we need to go to the target of the jump, so highlight the new line and hit the Enter Key.  But before we go, let’s make note of the next line’s address, which here is 049E9693.

 

We will overwrite the slack space bytes we need with the new code for multiple skill points per level.  First, let’s figure out how many points we want per level, say, 3.  Then we will multiply that with EBX, the number of levels gained.  Finally we put the three PUSHes in that we overwrote, and jump back to the following line’s address.

 

MOV EAX,3

IMUL EAX,EBX

PUSH 0

PUSH EAX

PUSH 5

JMP 049E9693

Use the Assemble feature and enter the lines just as I wrote them above.  You should now have:

 

 

Let’s put a breakpoint on line 049E967C and watch what happens on the stack.  We will use the Step Over feature (key F8) as we don’t want to go into the add_stat function.  Now go out and gain a level.  (It is best to use a very low level character for this test.)  Once we come up against the breakpoint, step down to the IMUL EAX,EBX line.  Look below the disassembly window and you will see the contents of both registers there:

 

 

We confirm that EBX contains 1, since we gained only one level.  Now continue down to the CALL line.  In the Stack window, you will see the four parameters set up on the stack and listed in reverse order.  Each PUSH has a similar effect to SUB ESP,4 so the stack grows upward:

 

 

Step over the CALL and follow the skill points assignment.  When you walk onto your new jump, a red line appears at left to show that you will take the jump when you step to the next instruction.  We load EAX with our new skill points per level quantity and come to the new IMUL instruction.  The inspection window shows:

 

 

just like we want it to be.  Continue on to the jump back to original code and then the next CALL.  The stack now has

 

 

A quick comparison of the two stack excerpts shows us we had similar looking parameters ahead of the add_stat function.  So we will step over this call, and with no unexpected event hit F9 to run the game.  Then go to the stat and skill panes and you should see:

 

 

Which is what we wanted to see.

 

This is good stuff, but how do we save it?  If you exit OllyDbg now, you will close the game without saving your character, similar to a crash.  And you will lose all your changes in memory!  Unfortunately, saving code edits in debugger mode is definitely not as straightforward as a File Save command.  First, you need to identify all your changes, which you do by right clicking and selecting Copy, then Select All:

 

 

Next, right-click again and choose Copy to Executable, then All Modifications:

 

 


You will get a popup dialog with four buttons, choose Copy All.

 

 

You will get a new window which resembles a DLL opened in File Assembly mode.  Right-click one more time and choose Save File.

 

 

Now you will get a standard Windows file save dialog, and you are home free.  Save the file as D2Game.dll (in this case) and accept the overwrite.  From now on, whenever you play the game with your customized module, you will gain 3 points per level (or whatever number you wish to choose).

 

There are many other features in OllyDbg, but these are the main ones and the ones you’ll probably use most often.

 


Conclusion

 

By this point you should understand hexadecimal, some basic assembly, and how to use XVI32, OllyDbg, and the Windows Calculator for you code editing purposes.  If you’re looking for more information or for things that can be changed, go to the Phrozen Keep’s code editing forum.  Just scroll down a little ways and you’ll find it.  Or use the Keep’s search feature to locate a specific change.

 

All in all, I think you’ll find that the most important thing to getting anywhere is to practice.  After a little while you’ll begin to know exactly what instructions do what and how all the instructions work together to form a function.  You’ll begin to know what to look for when you want to change things and how to find it.  All you need to do is practice.  The best place to start is by going to the Phrozen Keep’s code editing forum and finding a few examples that list the ASM code.  Then look at it and try and follow along with what they changed.  It probably won’t be as hard as you expect it to be.  And it will help you to see how things are usually changed.

 


Credits

 

Myhrginoc thanks:

 

Sir_General, who wrote the original tutorial I expanded and updated.

Nefarius, for reviewing this version of the tutorial and many suggestions.

Blizzard Entertainment, for the Diablo Universe and two-and-a-half great games in it.

 

 

Sir_General’s original list from v1.00 of this tutorial:

 

I’m sure I can’t think of everyone who deserves a spot in the credits, but I’ll certainly try.  If I left you out, please e-mail me at sir_general@planetdiablo.com and I’ll make sure to add you in.

 

Special thanks to:

 

Jarulf

FoxBat

Apocalypse Demon

RicFaith

Myhrginoc

Alkalund

Maxbogus

Thunder

 

And last of all…

 

The Phrozen Keep and their host

PlanetDiablo at the GameSpy Network