• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • ✅ Time to vote for the top 3 models! The POLL for Hive's 6th HD Modeling Contest: Mechanical is now open! 📅 Poll close on July 16, 2024! 🔗 Cast your vote now!
  • ✅ The POLL for Hive's Texturing Contest #33 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!

Accessing memory from the script - it's time of the revolution

Status
Not open for further replies.

pyf

pyf

Level 32
Joined
Mar 21, 2016
Messages
2,985
Probably not as XP is kind of not supported anymore. Would mean having to use older, pre Vista APIs to do stuff in general.
Are you the developer of War3Loader?

Fyi, it would seem the widescreen fix made by @ENAleksey as a .mix file, does work with v1.26a on XP SP3 with my French version of the game. I will test his fix some other day with v1.27a and v1.27b.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,233
Are you the developer of War3Loader?
No, but if it was easy then surely the developer would have made it support XP already? As a software engineer I know that Windows changed a lot of its APIs between XP and Vista to make them more future proof, and these new APIs are not supported by XP. Microsoft has dropped pretty much all support for XP by now.
 

pyf

pyf

Level 32
Joined
Mar 21, 2016
Messages
2,985
Well, clicking on the appropriate checkbox in Visual Studio 2015/2017 so that it produces an exe compatible with XP, would be a first step.

I notice that War3Loader.exe seems to call the debug libraries, if I understand correctly what VirusTotal says about its PE imports.

Fyi, the current version of the Microsoft Redistributable for Visual Studio 2017 is 14.10.25017, and it installs just fine on XP SP3. VS 2017 builds upon and expands VS 2015's features.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,233
Well, clicking on the appropriate checkbox in Visual Studio 2015/2017 so that it produces an exe compatible with XP, would be a first step.
Which requires one to download and install the XP compatibility module of VS, something not necessarily done by default (unless installing on XP for obvious reasons). If using C++ one then needs to change the windows version define macro to be appropriate for XP so that a suitable windows header is used (defaults to Windows 10). One then needs to remove all Windows Vista and later API calls, which might reduce compatibility for Vista and later operating systems as there is a reason they were added in the first place. It can turn out to be as simple as a rebuild, but also it could turn out to be impossible.
 

pyf

pyf

Level 32
Joined
Mar 21, 2016
Messages
2,985
War3Loader requires the Microsoft Visual Studio 2017 dll files in order to run.

Such dll files come in two flavors: the ones for the end-users, and the ones for the developers. The name of the ones for the developers end with a 'D'. They are useful for debugging purposes.


Now if you have a look at the VirusTotal report, under the tab 'File Detail', paragraph 'PE Imports', you will notice that the executable uses the developer version of the dlls, not the regular end-user ones.

The problem is, the developer version of these files are not included in any of the Visual Studio 2015/2017 Redistributables afaik.


Fyi, the latest version of the 2017 Runtime for end-users can be downloaded from here:
Téléchargements | IDE, Code et Team Foundation Server | Visual Studio
(under 'Other Tools and Frameworks')

It supersedes any of the 2015 runtimes.
 

pyf

pyf

Level 32
Joined
Mar 21, 2016
Messages
2,985
This new version now also works on XP SP3 (tested with Patch 1.26a, French version).
Thanks a lot. :thumbs_up:


For some reason, the first time I launched War3Loader, it did not load its plugin ('Checking for plugins...' / 'No plugins were found' in the debug txt file). Therefore, proper widescreen worked in the Campaign, but not the transparency effect with the UI.
So I then quit the game, launched it again and this time I loaded your demo map. The transparency stuff worked.
Since then, the plugin seems to always load as expected, and therefore the transparency effect now works always. Even where it did not the first time. Go figure... Does your loader write something in the Registry?

Should you want to develop this loader some more, then maybe you could add the possibility to launch RoC, for those who want to play its solo campaign?


** edit **

Microsoft has dropped pretty much all support for XP by now.
@Dr Super Good:
- All You Need to Know About the WannaCrypt Ransomware - MajorGeeks
"Due to the scale of the attack, to deal with the unsupported Windows systems and to contain the spread of the ransomware, Microsoft has taken the unusual step of releasing updates for all older unsupported operating systems from Windows XP onwards."
- http://www.catalog.update.microsoft.com/Search.aspx?q=KB4012598


Plus, about the POSReady 2009 updates:
- POSReady 2009 updates ported to Windows XP SP3 ENU
(unofficial, and unsupported by Microsoft)
 
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
Hey, I have tested it. It's working, but sometimes the map crashed when loading. Only sometimes...
Btw, this is supposed to work on 1.28.1 and above right?

Yes it is, although there was a small mistake of my part (forgot to remove a "return" instruction), I have just tested it under 1.28.5 and it worked.
I have uploaded a new version of the TestMap, please take a look on my post at the other thread.

However, if you say it's crashing "only sometimes" for you, it means that the searching algorithm might be hitting some false positives. I will make a better Testmap, that outputs Debug messages to a file via Preloader native. This will help me to detect the problem.
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
After replacing pjass.exe I imported to clean map: Typecast, Memory, Version, Bitwise, ObjectData, Utils libraries (from 1st post). Got error on saving: "Undeclared function Int2Hex()"
in library "Version". Do I need additional lib?
Just comment out all BJDebugMsg in a function called "StackSearcher".

BTW, if you have tested this version, please let me know whether it's crashing or not. The crash only happens sometimes tho.
 
Thanks, Ill report how it works on my pc

edit
tested all functions from Utils lib except GetAbilityCurrentCooldown
all works fine, no crashes
Im using Jass NewGen and War3 version 1.27.1.7085

edit2
it would be very nice if we have here such a functions (like in MemoryHack) for example
IsUnitStunned2
IsAbilityOnCooldown
GetUnitAttackType1
GetUnitAttackType2
GetUnitAttackSpeed
 
Last edited:

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
@ZiBitheWand3r3r for me the crash only happened when map's loading. If it loads successfully, all functions gonna work fine.
This is strange, I use the same patch version as you. I tried to paste all the memory hack triggers into an empty map but the crash persists :/

EDIT:
I have found the problem. I tried to re-copy-paste all the codes and the crash seems to gone, literally. I took some times to dig in and find out what's the cause. And what I found was weirder than fiction (at least for me):
- If I do not modify anything in Version libs (other than deleting some debug messages that uses Int2Hex), it works fine.
- If I removed line: call BJDebugMsg("Warning! Unsupported version!\nVersion-specific offsets have not been initialized!") in Init function, it results in crash.
- If I replace it with other function like DisplayTimedTextToPlayer, it also results in crash.
- If I add anything to that block like:
JASS:
    if i == 2894996 then
        set PatchVersion = "1.28"
        call Init28()
    elseif i == 2889044 then
        set PatchVersion = "1.27b"
        call Init27b()
    elseif i == 2586768 then
        set PatchVersion = "1.27"
        call Init27()
    elseif i == 5205600 then
        set PatchVersion = "1.26"
        call Init26()
    else
=> it also results in crash.

In short if I modify anything in that function, it will result in crash. I have re-confirmed it over and over again, and that's really what's happening here. I literally have no idea what's going on here.
 
Last edited:
- If I removed line:
call BJDebugMsg("Warning! Unsupported version!\nVersion-specific offsets have not been initialized!")
in Init function, it results in crash.
- If I replace it with other function like
DisplayTimedTextToPlayer
, it also results in crash.
I did the same 2 steps as you with no crash (tried few times to run war3),
do you have jass parser file as linked in first post? I do,
did you move/delete commands //# +nosemanticerror?
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
Level 9
Joined
Jul 30, 2012
Messages
156
In short if I modify anything in that function, it will result in crash. I have re-confirmed it over and over again, and that's really what's happening here. I literally have no idea what's going on here.

This is what's going on here:
JASS:
exitwhen pJassContext*65536/65536 == 0x88 and pJassContext>65536 and Memory[pJassContext/4+8] == f+888

Basically I use VM's instruction pointer (pJassContext/4+8) to verify that the candidate address is really the Jass Context that I want. The problem is, I am comparing that address to an offset from function Init (f+888). So, if you modify ANYTHING in function Init, that offset will change, and stack searching will fail, causing a crash.

But don't worry, I've already got a better search method, wait for it in the next hours.
 
Level 14
Joined
Oct 18, 2013
Messages
702
Basically I use VM's instruction pointer (pJassContext/4+8) to verify that the candidate address is really the Jass Context that I want. The problem is, I am comparing that address to an offset from
function Init
(f+888). So, if you modify ANYTHING in function Init, that offset will change, and stack searching will fail, causing a crash.

But don't worry, I've already got a better search method, wait for it in the next hours.

If this much work was going into Memory Hack development, we'd truly have some wonderful things by now.
 
Level 9
Joined
Jul 30, 2012
Messages
156
Updated Version library, please test the new code. I have removed the debug messages, and I'm no longer using instruction pointer for verification, so you are free to make some edits to the code.

If this much work was going into Memory Hack development, we'd truly have some wonderful things by now.

This is the basis of memory hack, we can do nothing without having the game offsets, and typing them manually every patch is not only tedious, but any map that uses memory hack will have to be manually updated every time. I'm in the process of migrating the whole memory hack to this method, so that manual updates will no longer be necessary, and it will most likely work in all upcoming versions of WC3.
 
Level 9
Joined
Jul 30, 2012
Messages
156
First step of detection is ready. Jass context and main game class are automatically detected now. Please test the attached map and tell me if it works properly. Soon I will be updating the other libraries to work with this code.
 

Attachments

  • AddressDetection.w3m
    25.2 KB · Views: 105

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
First step of detection is ready. Jass context and main game class are automatically detected now. Please test the attached map and tell me if it works properly. Soon I will be updating the other libraries to work with this code.
I retried several times. It works, no crash :)
 
Level 9
Joined
Jul 30, 2012
Messages
156
I commented:
library Version initializer Init requires Memory//, HexNumber because I have no HexNumber lib
game crashes everytime I call GetUnitArmor, GetHeroPrimaryAttribute or IsUnitStunned,
warcraft ver 1.27.1.7085.
(I had no problems with previous libraries)

Sorry, I forgot to divide GameState by 4 :p

JASS:
function ConvertHandle takes handle h returns integer
    return Memory[Memory[Memory[Memory[GameState/4]/4+7]/4+103]/4 + GetHandleId(h)*3 - 0x2FFFFF] 
endfunction
 
Level 9
Joined
Jul 30, 2012
Messages
156
must be something more to fix, it still crashes

Indeed, there was 1 "Memory[]" read more than the necessary. Now it is working.
Thanks for reporting, I have changed the library in the OP.
JASS:
function ConvertHandle takes handle h returns integer
    return Memory[Memory[Memory[GameState/4+7]/4+103]/4 + GetHandleId(h)*3 - 0x2FFFFF] 
endfunction
 
This ability to access memory is amazing! Even overkill at making the life of the coder much more comfortable. Thank you, @leandrotp for working on this library. But, ...

Is there a way to access Gameplay Constants in-game, using Memory Hack? (without having to create a global and assigning the same value of a certain data field to that global)

On another note, I found some of the offsets that may prove useful to the coder, and ported some from the Memory Hack thread:

JASS:
    function GetUnitAttackAbility takes unit u returns integer
        return Memory[ConvertHandle(u)/4+0x1e8/4]
    endfunction

    function GetUnitBaseDamage takes unit u returns integer
        return Memory[GetUnitAttackAbility(u)/4 + 0xA0/4]
    endfunction

    function GetUnitAttackSpeed takes unit u returns real
        return RMemory[GetUnitAttackAbility(u)/4 + 0x1B0/4]
    endfunction

    function GetUnitAttackSpeedSec takes unit u returns real
        local real r = GetUnitAttackSpeed(u)
        return 1 / r
    endfunction

    function GetUnitAttackBackswing takes unit u returns real
        return RMemory[GetUnitAttackAbility(u)/4 + 0x190/4]
    endfunction

    function GetUnitZ takes unit u returns real
        return RMemory[ConvertHandle(u)/4 + 0x28C/4]
    endfunction

    // I made this type-safe, thanks for the 'AHer' ability field!
    function GetHeroStatGain takes unit u, integer attribute returns real
        if GetUnitAbilityLevel(u, 'AHer') != 0 then
            return RMemory[Memory[ConvertHandle(u)/4 + 0x1F0/4]/4 + 0xCC/4 + 2*attribute]
        endif
        return 0.
    endfunction

    function CountUnitsInGroupEx takes group g returns integer
        return Memory[ConvertHandle(g)/4 + 0x34/4]
    endfunction
   
    function IsUnitEthereal takes unit u returns boolean
        return Memory[ConvertHandle(u)/4 + 0x1C0/4] != 0
    endfunction
 
Level 9
Joined
Jul 30, 2012
Messages
156
Code has been updated, please check the changelong and get the updated versions of all libraries.

Library Version's 'integer GameState' variable doesn't seem to get initialized in 1.28.5 (it does in 1.26).

Are you sure? I always test it in 1.28.5 before releasing and it was working just fine. Try the new code now, things changed a bit but it should be working as well, I tested here and it worked.

yes, it works perfectly for me now, thanks, IsUnitStunned if really cool function ;)
by the way , I tryed GetUnitFlags(u) game prints 3..4 digit integers. Can you explain what it shows?

I don't remember exactly, that function is from the time me and Draco were messing with memory hack and discovering new stuff. That integer contains a lot of flags, things like invisibility, invulnerability and so on. I don't recall the meaning of each flag, maybe someday I will make functions like IsUnitInvisible that make use of it. Maybe you can find some info at Draco's Memory hack thread too.

Can you please provide an older script code for mouse natives? They worked nicely and I can't find the code anymore. No need for any "mouseEnv".

The old code was using fixed memory offsets, and because of that it only worked up to 1.28.1, since I needed to manually update offsets each time a patch was released. As I said I am migrating everything to automatic detection, so the mouse functions are disabled until I write the code to detect the mouse offsets.

But don't worry, I've already got a method to find these offsets, soon I will be releasing an implementation. It won't be too long, I promise.

Is there a way to access Gameplay Constants in-game, using Memory Hack? (without having to create a global and assigning the same value of a certain data field to that global)

Of course! Any information that is used by the game can also be retrieved with memory access, it's just a matter of finding where it is. For now I am still developing the basis of memory hack (address detection and base API), but in the future things like this shall be implemented and much more! Maybe gameplay constants are stored in a place that will require me to detect another game offset, I don't know, but it shouldn't be much of a problem.

Also don't forget that you have Draco's thread as a reference, even though his code is a bit messy he made a huge progress in many areas, and you can port many functions from his code to this library if they use offsets that are already being detected.
 

~El

~El

Level 17
Joined
Jun 13, 2016
Messages
560
Code has been updated, please check the changelong and get the updated versions of all libraries.



Are you sure? I always test it in 1.28.5 before releasing and it was working just fine. Try the new code now, things changed a bit but it should be working as well, I tested here and it worked.



I don't remember exactly, that function is from the time me and Draco were messing with memory hack and discovering new stuff. That integer contains a lot of flags, things like invisibility, invulnerability and so on. I don't recall the meaning of each flag, maybe someday I will make functions like IsUnitInvisible that make use of it. Maybe you can find some info at Draco's Memory hack thread too.



The old code was using fixed memory offsets, and because of that it only worked up to 1.28.1, since I needed to manually update offsets each time a patch was released. As I said I am migrating everything to automatic detection, so the mouse functions are disabled until I write the code to detect the mouse offsets.

But don't worry, I've already got a method to find these offsets, soon I will be releasing an implementation. It won't be too long, I promise.



Of course! Any information that is used by the game can also be retrieved with memory access, it's just a matter of finding where it is. For now I am still developing the basis of memory hack (address detection and base API), but in the future things like this shall be implemented and much more! Maybe gameplay constants are stored in a place that will require me to detect another game offset, I don't know, but it shouldn't be much of a problem.

Also don't forget that you have Draco's thread as a reference, even though his code is a bit messy he made a huge progress in many areas, and you can port many functions from his code to this library if they use offsets that are already being detected.

This reminds me, on a slightly tangetial note. I was meaning to ask someone this. Excuse me if you've been asked this a ton of times already.
How do you find the offsets for each version? Do you use signature scanning for it, or have you been doing it manually in disassembly or something?
Can you please give a general overview of the procedure you use to update the offsets? Just what tools you use and the general outline of the process, not asking for a detailed tutorial, haha.

I've just been recently looking into a certain, but very useful tool for WC3 called Lua Engine (I think it's on d3scene), but it's outdated and it uses fixed offsets (a few hundred of them), which have obviously broken with new versions, and I'm looking to update it for the latest version of WC3 since I can use it for my map. You'd help me immensely if you gave me some pointers in which direction I should dig.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Are you sure? I always test it in 1.28.5 before releasing and it was working just fine. Try the new code now, things changed a bit but it should be working as well, I tested here and it worked.
When compiling in debug mode (vJass) it doesn't work (the GameState variable is not initialized), but in release mode (vJass's inliner kicks in) it does work. I tend to always "stay" in debug mode =)...
 
Level 9
Joined
Jul 30, 2012
Messages
156
This reminds me, on a slightly tangetial note. I was meaning to ask someone this. Excuse me if you've been asked this a ton of times already.
How do you find the offsets for each version? Do you use signature scanning for it, or have you been doing it manually in disassembly or something?
Can you please give a general overview of the procedure you use to update the offsets? Just what tools you use and the general outline of the process, not asking for a detailed tutorial, haha.

I've just been recently looking into a certain, but very useful tool for WC3 called Lua Engine (I think it's on d3scene), but it's outdated and it uses fixed offsets (a few hundred of them), which have obviously broken with new versions, and I'm looking to update it for the latest version of WC3 since I can use it for my map. You'd help me immensely if you gave me some pointers in which direction I should dig.

Having a ton of experience in this area, I can tell you that there isn't a standard step-by-step way that will work for every case. Actually what introduced me to the programming world many years ago was exactly this matter: I got the source code of a hack for the game Gunz: The Duel and, having never programmed anything in my life, I managed to find the updated offsets and recompile the hack for a newer version of the game client.

Obviously I didn't figure everything just by myself. Someone on those forums had posted a simple tutorial of finding some offsets in gunz.exe through signature searching. It worked on that particular case, but this method isn't guaranteed to work everywhere, especially in the case of WC3.

More important than specific byte signatures, is the ability to recognize similar code patterns across different versions. This requires some disassembly and reverse engineering skills, but it's something you'll be acquiring with time. Let's take a look at an example:

1.png

This is the disassembly of the native GetUnitPointValueByType from patch 1.26. As you can see it hashes the rawcode, then calls GetObjectData passing the UnitData pointer as the 1st argument, in the ECX register.

Now let's take a look at the same native from patch 1.27a:

2.png

Looks completely different, doesn't it? Where's the call to GetObjectData, and what is all this new code after IntegerHash? Did Blizzard change this function so drastically?

Now just take a look at GetObjectData itself, from 1.26:

3.png

Can you see the similarities? The code that comes after the IntegerHash call looks very similar to the code of GetObjectData itself. With the difference that we now have MOV EAX,[67B1C494] in the place of MOV EAX,[ECX+24] and MOV ECX,[67B1C48C] instead of MOV EDX,[ECX+1C]

The reason looks simple now: the call to GetObjectData has been inlined! And this is not something unexpected, because Blizzard changed the compiler for the 1.27 patch, migrating to a newer version of Visual Studio, which has a more aggressive optimizer. Many other function calls have been inlined like this.

So we can deduce, if [67B1C494] is [ECX+24], and [67B1C48C] is [ECX+1C], then the address of pUnitData is certainly 67B1C470. Subtract it from the base of game.dll (at this time it was 66F30000) and the result is 0xBEC470, the offset of the UnitData table for patch 1.27.

Well I suppose I did nothing but complicate the things for you until now right? As I said this job inevitably requires good disassembly skills, but I can give you some hints:

First step: Open game.dll from 1.26 and go to the offset you want. See if it's located in code or data sections, and find references to it.

As an example, I'll use the offset of MouseEnv object (game.dll+AB4F80), which gives access to mouse functions.

4.png

As you can see this object has 5 references, and 3 of them are in the same function. So the deal is, if we find this function in patch 1.27, we got our address.

Second step: now what you need to do is find something in this function that could possibly be used to search for it in the new patch. Strings and constants are always good candidates, WC3 has a lot of debug information in it's executables, like classes and source file names, and sometimes they helps us to find functions across versions.

6.png

Well, it found 42 references to the string "CGameUI.cpp". This is not so bad, in the worst case we could look at those functions one by one, and see if we find something more-or-less similar to what we are looking for. But let's try something different, the constant 1AAF:

7.png

Wow, we've got it! Only one reference, and it's exactly what we want! Just compare the two functions, and see that they are very similar. Some small changes here and there, like a conditional move (CMOVNZ) instruction in the place of a Jump, but as I said, this is just compiler optimization.

If you want more confirmation, just look at the function calls: they both are calling Storm.#401 with exactly the same parameters, and after that an object constructor (67279FA0) is called, which is also very similar to the one from 1.26 (6F2FE9F0) This is definitely the function we want.

So the address of MouseEnv in this version is 67B16350, subtract from base of game.dll and we get 0xBE6350.

If you cannot locate something noticeable that you can use to search, or if the search yields too many results, you can try looking for the callers and callees of that function. Try repeating the process above with a function that calls or is called by the function you want, and if succeed you will most likely find it too.

If the address you're looking is located in a Jass native, or a function that is called by a native, then it's bingo! All natives have well-defined addresses, and you can easily locate them through their name:

5.png

That is the initializer of the Jass natives table. The address of the native is in the MOV ECX instruction, right below the native name. In this case it is 6F3B2E20 for the native GetUnitPointValueByType. You can easily find any native this way.

Well, these are some methods that I use to find offsets myself. Other things include looking at objects' vTables and searching for generic instructions (OllyDbg allows to search for generics like "MOV R32, [R32+24]" which returns any MOV instruction that fits this pattern, independently of the used registers). As I said, every case is specific, and it all depends on your skills.

I'm pretty sure you'll learn a lot if you really dig into this subject. Reading some assembly lessons is a good starting point (it is how I started myself, learned ASM before any other language). And don't be afraid to ask for any further help, if I don't help you, certainly someone else will.
 
Last edited:

~El

~El

Level 17
Joined
Jun 13, 2016
Messages
560
Having a ton of experience in this area, I can tell you that there isn't a standard step-by-step way that will work for every case. Actually what introduced me to the programming world many years ago was exactly this matter: I got the source code of a hack for the game Gunz: The Duel and, having never programmed anything in my life, I managed to find the updated offsets and recompile the hack for a newer version of the game client.

Obviously I didn't figure everything just by myself. Someone on those forums had posted a simple tutorial of finding some offsets in gunz.exe through signature searching. It worked on that particular case, but this method isn't guaranteed to work everywhere, especially in the case of WC3.

More important than specific byte signatures, is the ability to recognize similar code patterns across different versions. This requires some disassembly and reverse engineering skills, but it's something you'll be acquiring with time. Let's take a look at an example:

View attachment 281150

This is the disassembly of the native GetUnitPointValueByType from patch 1.26. As you can see it hashes the rawcode, then calls GetObjectData passing the UnitData pointer as the 1st argument, in the ECX register.

Now let's take a look at the same native from patch 1.27a:

View attachment 281144

Looks completely different, doesn't it? Where's the call to GetObjectData, and what is all this new code after IntegerHash? Did Blizzard change this function so drastically?

Now just take a look at GetObjectData itself, from 1.26:

View attachment 281145

Can you see the similarities? The code that comes after the IntegerHash call looks very similar to the code of GetObjectData itself. With the difference that we now have MOV EAX,[67B1C494] in the place of MOV EAX,[ECX+24] and MOV ECX,[67B1C48C] instead of MOV EDX,[ECX+1C]

The reason looks simple now: the call to GetObjectData has been inlined! And this is not something unexpected, because Blizzard changed the compiler for the 1.27 patch, migrating to a newer version of Visual Studio, which has a more aggressive optimizer. Many other function calls have been inlined like this.

So we can deduce, if [67B1C494] is [ECX+24], and [67B1C48C] is [ECX+1C], then the address of pUnitData is certainly 67B1C470. Subtract it from the base of game.dll (at this time it was 66F30000) and the result is 0xBEC470, the offset of the UnitData table for patch 1.27.

Well I suppose I did nothing but complicate the things for you until now right? As I said this job inevitably requires good disassembly skills, but I can give you some hints:

First step: Open game.dll from 1.26 and go to the offset you want. See if it's located in code or data sections, and find references to it.

As an example, I'll use the offset of MouseEnv object (game.dll+AB4F80), which gives access to mouse functions.

View attachment 281146

As you can see this object has 5 references, and 3 of them are in the same function. So the deal is, if we find this function in patch 1.27, we got our address.

Second step: now what you need to do is find something in this function that could possibly be used to search for it in the new patch. Strings and constants are always good candidates, WC3 has a lot of debug information in it's executables, like classes and source file names, and sometimes they helps us to find functions across versions.

View attachment 281148

Well, it found 42 references to the string "CGameUI.cpp". This is not so bad, in the worst case we could look at those functions one by one, and see if we find something more-or-less similar to what we are looking for. But let's try something different, the constant 1AAF:

View attachment 281149

Wow, we've got it! Only one reference, and it's exactly what we want! Just compare the two functions, and see that they are very similar. Some small changes here and there, like a conditional move (CMOVNZ) instruction in the place of a Jump, but as I said, this is just compiler optimization.

If you want more confirmation, just look at the function calls: they both are calling Storm.#401 with exactly the same parameters, and after that an object constructor (67279FA0) is called, which is also very similar to the one from 1.26 (6F2FE9F0) This is definitely the function we want.

So the address of MouseEnv in this version is 67B16350, subtract from base of game.dll and we get 0xBE6350.

If you cannot locate something noticeable that you can use to search, or if the search yields too many results, you can try looking for the callers and callees of that function. Try repeating the process above with a function that calls or is called by the function you want, and if succeed you will most likely find it too.

If the address you're looking is located in a Jass native, or a function that is called by a native, then it's bingo! All natives have well-defined addresses, and you can easily locate them through their name:

View attachment 281147

That is the initializer of the Jass natives table. The address of the native is in the MOV ECX instruction, right below the native name. In this case it is 6F3B2E20 for the native GetUnitPointValueByType. You can easily find any native this way.

Well, these are some methods that I use to find offsets myself. Other things include looking at objects' vTables and searching for generic instructions (OllyDbg allows to search for generics like "MOV R32, [R32+24]" which returns any MOV instruction that fits this pattern, independently of the used registers). As I said, every case is specific, and it all depends on your skills.

I'm pretty sure you'll learn a lot if you really dig into this subject. Reading some assembly lessons is a good starting point (it is how I started myself, learned ASM before any other language). And don't be afraid to ask for any further help, if I don't help you, certainly someone else will.

Man, this is great! More than I'd ever hope for, and it certainly helps me get started. This subject always felt like somewhat arcane to me, even though I have a decent grasp on low-level concepts, and most of what you said made perfect sense to me. Although, I could never quite figure out how to even approach this, but this helped a lot. It's how they say, the hardest thing is to get started. Huge, huge, huge thanks to you!

This really inspires me to go and dig into this myself.
 
Having a ton of experience in this area, I can tell you that there isn't a standard step-by-step way that will work for every case. Actually what introduced me to the programming world many years ago was exactly this matter: I got the source code of a hack for the game Gunz: The Duel and, having never programmed anything in my life, I managed to find the updated offsets and recompile the hack for a newer version of the game client.

Obviously I didn't figure everything just by myself. Someone on those forums had posted a simple tutorial of finding some offsets in gunz.exe through signature searching. It worked on that particular case, but this method isn't guaranteed to work everywhere, especially in the case of WC3.

More important than specific byte signatures, is the ability to recognize similar code patterns across different versions. This requires some disassembly and reverse engineering skills, but it's something you'll be acquiring with time. Let's take a look at an example:

View attachment 281150

This is the disassembly of the native GetUnitPointValueByType from patch 1.26. As you can see it hashes the rawcode, then calls GetObjectData passing the UnitData pointer as the 1st argument, in the ECX register.

Now let's take a look at the same native from patch 1.27a:

View attachment 281144

Looks completely different, doesn't it? Where's the call to GetObjectData, and what is all this new code after IntegerHash? Did Blizzard change this function so drastically?

Now just take a look at GetObjectData itself, from 1.26:

View attachment 281145

Can you see the similarities? The code that comes after the IntegerHash call looks very similar to the code of GetObjectData itself. With the difference that we now have MOV EAX,[67B1C494] in the place of MOV EAX,[ECX+24] and MOV ECX,[67B1C48C] instead of MOV EDX,[ECX+1C]

The reason looks simple now: the call to GetObjectData has been inlined! And this is not something unexpected, because Blizzard changed the compiler for the 1.27 patch, migrating to a newer version of Visual Studio, which has a more aggressive optimizer. Many other function calls have been inlined like this.

So we can deduce, if [67B1C494] is [ECX+24], and [67B1C48C] is [ECX+1C], then the address of pUnitData is certainly 67B1C470. Subtract it from the base of game.dll (at this time it was 66F30000) and the result is 0xBEC470, the offset of the UnitData table for patch 1.27.

Well I suppose I did nothing but complicate the things for you until now right? As I said this job inevitably requires good disassembly skills, but I can give you some hints:

First step: Open game.dll from 1.26 and go to the offset you want. See if it's located in code or data sections, and find references to it.

As an example, I'll use the offset of MouseEnv object (game.dll+AB4F80), which gives access to mouse functions.

View attachment 281146

As you can see this object has 5 references, and 3 of them are in the same function. So the deal is, if we find this function in patch 1.27, we got our address.

Second step: now what you need to do is find something in this function that could possibly be used to search for it in the new patch. Strings and constants are always good candidates, WC3 has a lot of debug information in it's executables, like classes and source file names, and sometimes they helps us to find functions across versions.

View attachment 281148

Well, it found 42 references to the string "CGameUI.cpp". This is not so bad, in the worst case we could look at those functions one by one, and see if we find something more-or-less similar to what we are looking for. But let's try something different, the constant 1AAF:

View attachment 281149

Wow, we've got it! Only one reference, and it's exactly what we want! Just compare the two functions, and see that they are very similar. Some small changes here and there, like a conditional move (CMOVNZ) instruction in the place of a Jump, but as I said, this is just compiler optimization.

If you want more confirmation, just look at the function calls: they both are calling Storm.#401 with exactly the same parameters, and after that an object constructor (67279FA0) is called, which is also very similar to the one from 1.26 (6F2FE9F0) This is definitely the function we want.

So the address of MouseEnv in this version is 67B16350, subtract from base of game.dll and we get 0xBE6350.

If you cannot locate something noticeable that you can use to search, or if the search yields too many results, you can try looking for the callers and callees of that function. Try repeating the process above with a function that calls or is called by the function you want, and if succeed you will most likely find it too.

If the address you're looking is located in a Jass native, or a function that is called by a native, then it's bingo! All natives have well-defined addresses, and you can easily locate them through their name:

View attachment 281147

That is the initializer of the Jass natives table. The address of the native is in the MOV ECX instruction, right below the native name. In this case it is 6F3B2E20 for the native GetUnitPointValueByType. You can easily find any native this way.

Well, these are some methods that I use to find offsets myself. Other things include looking at objects' vTables and searching for generic instructions (OllyDbg allows to search for generics like "MOV R32, [R32+24]" which returns any MOV instruction that fits this pattern, independently of the used registers). As I said, every case is specific, and it all depends on your skills.

I'm pretty sure you'll learn a lot if you really dig into this subject. Reading some assembly lessons is a good starting point (it is how I started myself, learned ASM before any other language). And don't be afraid to ask for any further help, if I don't help you, certainly someone else will.

Quite a nice approach to something low-level. Reading that also made me want to go about this memory thing myself, too. However, while using the debugging software, should warcraft 3 be running?

Another thing, how did you get the index of the natives? (Some arbitrary R for a native N)
 
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
When compiling in debug mode (vJass) it doesn't work (the GameState variable is not initialized), but in release mode (vJass's inliner kicks in) it does work. I tend to always "stay" in debug mode =)...

I found the reason, it's because I'm invisibly "returning" a value from function Step1, even though it returns nothing, I'm manipulating R0 to actually get a value passed back to bytecode. But when inliner is disabled, the Array sets become function calls, and every function call resets the value of R0, causing the VM crash when trying to access it. I'm gonna fix it in the next release

Quite a nice approach to something low-level. Reading that also made me want to go about this memory thing myself, too. However, while using the debugging software, should warcraft 3 be running?

Another thing, how did you get the index of the natives? (Some arbitrary R for a native N)

It doesn't need to be running, OllyDbg has the option to open a dll file directly for analysis, so you don't need to run wc3 at all, you can open game.dll directly with OllyDbg and see everything.

But it's possible to use it with the game running too! You can launch WC3 directly from OllyDbg, or launch it from WE and attach the debugger to it. But keep in mind that the game must ALWAYS be running in window mode if you're going to do that. If you're using NewGen it's better, as it can already launch WC3 in window mode.

If you forget to run in window mode, and the game gets paused by the debugger, you'll be unable to ALT-TAB out of the game screen, and you'll be forced to restart your computer! So don't make that mistake.

Also, what do you mean by "index of natives"? I didn't understand.

Very nice explanation.
Making hacks for Gunz was certainly a fun time. By now I don't remember anything about reverse engineering though :(

Did you make gunz hacks too? Did you release them? I'm Anônimo from GamerzPlanet (http://www.gamerzplanet.net/members/anônimo.2818941/).
 
Last edited:
Level 2
Joined
Oct 3, 2017
Messages
26
It's more convinient to read the code as C. Just take a look at GitHub - DracoL1ch/WC3Memory game126.c
That's way it's much easier to see constants, look for a strings, debug info, etc. IDA does this stuff, althought it's payed software, the best, with ExtraPass plugin. Converted code is much easier to compare between versions as well, if it's not that drastical jump as from 26 to 27.

Many constants are still searchable by patterns, offsets, etc. Just in case if you dont like byte-searching (like me) you can take this approach. It's not easier in common sense, it's just more human-like. Leandro the best, of course, but simple man needs simple solutions :)
 
Status
Not open for further replies.
Top