(if you don't want to follow the tutorial and just want the patch, scroll to the end of this post)
In this tutorial we are going to take a look at 1 possible way to get rid of it. If you take a look at experience penalty sequence at https://maxroll.gg/d2/resources/experience for example you can notice sequence 256, 256, 207, 159, 110, 61, 13. If you are more into code editing already you might be also aware of D2Moo project which is re-implementation of the Diablo2. in this project there is function called SUNITDMG_ComputeExperienceGain where you can see the full version of the same sequence: 256, 256, 256, 256, 256, 256, 207, 159, 110, 61, 13. Those multiple 256s at the beginning just represent the fact that the experience penalty only starts when monster level is more than 5 levels different from player level.
So now that we know what to look for, lets try to find this sequence from Diablo 2 game and then lets see what we can do about it. To do this we can use a program called Cutter. Cutter is useful lightweight static analysis program.
After you have opened your game.exe in cutter:
1) Click Search tab on bottom tab bar,
2) Enter the sequence 256, 256, 256, 256, 256, 256, 207, 159, 110, 61, 13,
3) From "Search for" choose "32bit value"
4) From "Search in" choose "All maps"
5) Click Search button.
The image below illustrates how to use Cutter:
Now it should show you the sequence it found from game.exe
6) Right click on found result
7) Click "Show X-Refs" menu option.
The image below shows how this menu should look like:
You should now see code that uses this sequence.
8) Double click the found reference to open it
Now you should see the code that uses this sequence to recalculate experience and apply penalties. The selected line is the line that uses this experience sequence. D2Moo equivalent for this is SUNITDMG_ComputeExperienceGain that I mentioned earlier.
9) Right click at the start of the function
10) Choose "Show X-Refs" from the menu
This should show you where this function itself is used.
11) Double click the found result to open it.
Again the line that is calling the function that we found earlier is highlighted.
The function that we found earlier takes arguments via ecx (nDefenderExperience), edx (nAttackerLevel) and eax (nDefenderLevel) and returns value via eax.
Hence mov eax, ecx simulates function returning nDefenderExperience. So all we have to do now is patch this function call to do just that.
To do that:
12) Right click the line that Cutter highlighted you.
13) From the menu choose Edit -> Instruction (14).
15) Type "mov eax, ecx" as a new instruction
You should now see mov eax, ecx in place of original function call and 3 nop instructions to fill up remaining space. This is because call instruction together with address took 5 bytes but mov eax, ecx only takes 2 bytes and therefore we have to do something with remaining 3 bytes. In this case we fill them with nops which stands for no operation.
Choose File -> Commit changes to save your changes to game.exe. Remember, this will overwrite your existing game.exe so remember to make backup if you haven't already so that you could later go back to original game.exe if you want.
Here is also a diff tool output of comparing the old and new game.exe file for those who just want to quickly apply the patch and not to do every step of this tutorial:
Code: Select all
$ radiff2 -O Game_old.exe Game.exe
0x0017e4c9 e822feffff => 89c8909090
You can now test your changes by killing some monsters at normal difficulty Blood Moor with level 30 character. You should now get 18 experience for killing fallen despite of being level 30 just like you would with level 1 character. It is important to understand that this tutorial only deals with experience penalty that comes from monsters being different level than player. There is still an other type of penalty that comes from Experience.txt column ExpRatio which applies additional experience penalty for characters level 70 and above. This means that if you test it with level 98 player you will still get less than 18 experience. To fix that also, set ExpRatio value to 1024 for all rows (1024 = 100%).
And finally, would like to thank Aytos from The Phrozen Keep Discord chat who helped me to learn about Cutter and how to use it. Without him this tutorial would not have happened. The purpose of writing this tutorial is to preserve what I have learned from The Phrozen Keep Discord and to show new learners step-by step how code editing looks like and to offer them hands on example of how to code edit Diablo 2.