Detect Game Version (GetPatchLevel)

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
Feel free to try and add detection for more patches. For example you could detect 1.28.1 by checking order id's (some got changed on that patch).

JASS:
local integer patch = GetPatchLevel()

if (patch == PATCH_LEVEL_127) then
    call BJDebugMsg("You are on patch 1.27b or lower")
elseif (patch == PATCH_LEVEL_128) then
    call BJDebugMsg("You are on patch 1.28")
elseif (patch == PATCH_LEVEL_129) then
    call BJDebugMsg("You are on patch 1.29")
elseif (patch == PATCH_LEVEL_130) then
    call BJDebugMsg("You are on patch 1.30.0 or 1.30.1")
elseif (patch == PATCH_LEVEL_1302) then
    call BJDebugMsg("You are on patch 1.30.2, 1.30.3, 1.30.4, or higher (bye hostbots)")
elseif (patch == PATCH_LEVEL_131) then
    call BJDebugMsg("You are on patch 1.31.0 or higher.")
endif
JASS:
library GameVersion
   
    globals
        constant integer PATCH_LEVEL_127    = 1
        constant integer PATCH_LEVEL_128    = 2
        constant integer PATCH_LEVEL_129    = 3
        constant integer PATCH_LEVEL_130    = 4
        constant integer PATCH_LEVEL_1302   = 5
        constant integer PATCH_LEVEL_131    = 6
    endglobals
   
    function GetPatchLevel takes nothing returns integer
        local image img
        local string tmp
        
        // This icon wasn't introduced until 1.28a
        set img = CreateImage("ReplaceableTextures\\WorldEditUI\\Editor-Toolbar-MapValidation.blp", 64, 64, 0, 0,0,0,64,64,0, 1)
        if (GetHandleId(img) == -1) then
            return PATCH_LEVEL_127 // 1.27b or lower
        endif
        call DestroyImage(img)

        // The array size limit was increased in 1.29, so if it's the same
        // then we are on 1.28.
        if (JASS_MAX_ARRAY_SIZE <= 8192) then
            return PATCH_LEVEL_128
        endif

        // This string didn't exist until 1.30
        if (GetLocalizedString("DOWNLOADING_MAP") == "DOWNLOADING_MAP") then
            return PATCH_LEVEL_129
        endif
       
        // This string changed in 1.30.2.
        set tmp = GetLocalizedString("ERROR_ID_CDKEY_INUSE")
        if (SubString(tmp, StringLength(tmp)-1, StringLength(tmp)) == ")") then // check the last character to presumably support all locales
            return PATCH_LEVEL_130
        endif
        
        set tmp = GetLocalizedString("WINDOW_MODE_WINDOWED")
        
        if (tmp == "WINDOW_MODE_WINDOWED") then
            return PATCH_LEVEL_1302
        endif
       
        return PATCH_LEVEL_131 // or higher
    endfunction
endlibrary
 

Attachments

  • GetPatchLevel.w3x
    17.7 KB · Views: 84
Last edited:
Level 8
Joined
Jul 30, 2012
Messages
153
This is really awesome! After memhack was patched, I thought it would be good if I could somehow detect the current patch, to see if the exploit was available or not, but the problem was, how could I try to detect anything without memory access at all? And you have just accomplished that. It's amazing!

In theory, this could be used to develop a "compatibility layer" for new generation WC3 maps. I mean, maps that are making use of 1.29+ natives, could have some sort of "abstraction layer" the reimplements the new natives using memhack, and then we could use this snippet to detect the game version at runtime, and dynamically choose to use either the natives or their memhack implementation, depending on what is available for the running patch.

If only I had a bit of time in my life to begin developing this...
 

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
In theory, this could be used to develop a "compatibility layer" for new generation WC3 maps. I mean, maps that are making use of 1.29+ natives, could have some sort of "abstraction layer" the reimplements the new natives using memhack, and then we could use this snippet to detect the game version at runtime, and dynamically choose to use either the natives or their memhack implementation, depending on what is available for the running patch.

Yeah that should work. Memory hacks can be loaded from a separate .ai file so it won't cause the JASS parser to throw any errors on the latest patch. There might be a problem getting the map to compile on the older patch without the new natives, but there might be a way.
 
Level 4
Joined
Sep 6, 2010
Messages
84
I thought I would wait for a version that has at least one editor for the UI of Warcraft 3 (by Blizzard), but I do not think the wait is needed anymore, thanks TriggerHappy. + (Up)
 

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
That's going to be tricky to implement, with the relatively small amount of background information on .ai files.

AI files are simply JASS scripts. They are pretty straight forward. The main function is executed and the script dies when it is finished. I attached a demo map which will load the memory hacks if a lower patch is detected. You will see a message box appear on supported memory hack versions (1.24b, 1.24e, 1.26a, 1.27a, 1.27b, 1.28a, and 1.28c). Otherwise the game will run normally. The tricky part is being able to use memory hack functions from outside of the AI script while still keeping the map compatible on the latest patch. I think it should be doable though.
 

Attachments

  • MemoryAPI.w3x
    27.2 KB · Views: 85
Level 25
Joined
Aug 18, 2009
Messages
4,081
Why is a dependency on one of the two core .j files inelegant...? Every map has a Blizzard.j in it.

With my jassAid tool for example that was not the case. All the functions of the blizzard.j were merged to the map script in order to transform it as a whole. Then the Scripts\blizzard.j path was overwritten with an empty file to not clash. Then again, the JASS_MAX_ARRAY_SIZE is in the Scripts\common.j, my mistake. I call it inelegant because it's not ensured and semantically off. Also, for instance, this assumes that every version before 1.29 had JASS_MAX_ARRAY_SIZE <= 8192, it's a blacklist approach.
 
Level 25
Joined
Aug 18, 2009
Messages
4,081
Why would one do that?

Do what? The jassAid tool injected debugging and safety functionality. The functions were sorted according to the call graph since jass can only call upwards and some of the call sites were automatically converted to TriggerEvaluate or the like. Log messages were inserted, some potentially crashing natives like Player were shielded. I wanted to detect thread breaks and have stack traces, so there was a need to know what's happening in the blizzard.j as well. You can do all kinds of optimizations like reducing identifiers or obfuscating them for map protection, making sure the local object variables are nullified, kicking unused or useless functions... Basically, any things a compiler would do.

Of course, you could have kept the blizzard.j and just get rid of the calls but it seemed more clean to me and I did not necessarily have to rename stuff/pay attention to name clashes. Also, I thought maybe the game not having to load the blizzard.j could be even more performant, you save global variables, etc.
 
Last edited:
Level 25
Joined
Aug 18, 2009
Messages
4,081
You mean you want to replace it in game files? For local use maybe, since you would desync it from other game instances. Yet this means you would play the game/maps not the way they were designed and this can easily introduce bugs. Also, keep in mind that jass has no concept of scope. When the maps were written, they paid respect to the blizzard.j. Agnostically inserting new global variables/functions might clash with declarations in the map script.
 

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
I updated the main post with detection for 1.30.2. I also am not creating an image to detect 1.29.2 anymore. I haven't tested this method on 1.30.0 though (I don't have it), but I assume it should work correctly there too. If it returns 129 on 1.30.0 then I will revert the old image detection method and add a 131 constant.

I call it inelegant because it's not ensured and semantically off. Also, for instance, this assumes that every version before 1.29 had JASS_MAX_ARRAY_SIZE <= 8192, it's a blacklist approach.

It's not going to be an issue most of the time. In which case would a patch before 1.29 not have constant set to 8192? I suppose someone could overwrite their common.j in their MPQ/CASC and change the value to try and trick the system, but why would someone do that? Chances are it would just lead to a desync in multiplayer. Not to mention if someone is hacking the game on an older patch and increasing the array limit then they can implement their own accurate patch detection.

If you really care there are a couple ways to avoid using the constant.
  1. JASS:
    local integer array arr
    set arr[8193] = 1
    if (arr[8193] == 0) then // might crash thread in older patches? idk
         return PATCH_LEVEL_128
    endif
  2. JASS:
    // This string changed slightly in 1.29 (quotes added around "Run as administrator")
    set tmp = GetLocalizedString("USER_DATA_MIGRATION_FAILED_ADMIN_BODY")
    if (SubString(tmp, 19, 20) == "R") then // probably only works for English clients.
        return PATCH_LEVEL_128
    endif
The only other way I can see a user abusing the system would be be creating an invalid image for "Editor-Toolbar-MapValidation.blp" which would return PATCH_LEVEL_127 regardless of which patch you were on. Again, it would likely just cause a desync depending on what the map maker is doing with the data. I never really planned on anyone using this in their maps anyway. It's kind of just proof of concept and at most I figured to be used for debugging.

May I ask, why not memoize the function? Evaluate on initialization, store the result then return the stored current patch version everytime the function is called.

It's in The Lab because I am just dumping the idea here and someone can do what they want with it.
 
Level 11
Joined
Oct 19, 2014
Messages
185
I been thinking a system like this,, because,, there are native function of common.j or blizzard.j that is really different to old version,, for example,, the Blz functions,, I wish I can freely use of those,, but seems there are wc3 players that still remain using the old version.. and the function of newer version that will Set and Get the damage of a unit is the awesomest function ever hahaha FUCK!!
 
Level 25
Joined
Aug 18, 2009
Messages
4,081
I been thinking a system like this,, because,, there are native function of common.j or blizzard.j that is really different to old version,, for example,, the Blz functions,, I wish I can freely use of those,, but seems there are wc3 players that still remain using the old version.. and the function of newer version that will Set and Get the damage of a unit is the awesomest function ever hahaha FUCK!!

JASS:
if (GetPatchLevel() < PATCH_LEVEL_129) then
    call EndGame(false)
endif

Well, the game won't load a map with natives that are unknown to it, anyway.
 
Level 14
Joined
Nov 18, 2012
Messages
1,372
I don't see anything useful in this. In what practical way would any one use this?
Even if a map used memhack, one could simply update it to last patch.
but seems there are wc3 players that still remain using the old version.. and the function of newer version that will Set and Get the damage of a unit is the awesomest function ever hahaha FUCK!!
Sticking in an old version forever is the recipe for total disaster.
Even if we have this latest unstable patch, the storm always gets worse before it finally is at ease and radically much better.
 

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
I don't see anything useful in this. In what practical way would any one use this?
Even if a map used memhack, one could simply update it to last patch.

Sticking in an old version forever is the recipe for total disaster.
Even if we have this latest unstable patch, the storm always gets worse before it finally is at ease and radically much better.

There's a reason it's in The Lab. It definitely has some uses, but mostly for testing/debug environments and in other rare cases where developers want to support multiple patches, regardless of whether or not you agree with it.
 
Level 6
Joined
Jan 17, 2010
Messages
149
Warning: this code can potentially cause a crash in warcraft version 1.26b ("memory cannot be 'written' " fatal error)

Possibly due to how CreateImage works. Crash is random, happens about 50% of the time. Once I deleted this code and run it about 15 times, no crashes were happening in any of these 15 runs.

Code:
// This icon wasn't introduced until 1.28a
set img = CreateImage("ReplaceableTextures\\WorldEditUI\\Editor-Toolbar-MapValidation.blp", 64, 64, 0, 0,0,0,64,64,0, 1)
if (GetHandleId(img) == -1) then
     return PATCH_LEVEL_127 // 1.27b or lower
endif
call DestroyImage(img)


EDIT: (1 day later)
I discovered that ReplaceableTextures\PassiveButtons\PASBTNWellSpring.blp does not exist in 1.31 but exists in current Beta of 1.32 (reforged)

Can use the aforementioned image trick to detect 1.32 now. From my experience, the image code doesn't crash in 1.31 so this might be a good way to detect the latest version. As long as they don't delete this new icon that is.

Paste this as the last if check
Code:
// This icon wasn't introduced until 1.32
set img = CreateImage("ReplaceableTextures\\PassiveButtons\\PASBTNWellSpring.blp", 64, 64, 0, 0,0,0,64,64,0, 1)
if (GetHandleId(img) == -1) then
     return PATCH_LEVEL_132 // 1.32b or higher
endif
// call DestroyImage(img) // not sure destroying an invalid image is necessary or even safe...
 
Last edited:
Level 8
Joined
Jul 10, 2008
Messages
354
Anyone know of a way to detect 1.26a ?

Maybe with the Preload() change that happened on 1.27 or something? (haven't fully understood the change)
 
Level 30
Joined
Feb 5, 2009
Messages
4,290
Wasn't sure where to put this, but I took inspiration from what was done here to check to see if someone was running Reforged or not. The reason for this was primarily because I wanted to explore the possibility of running separate code for Reforged Users and Non-Reforged Users in the event of something that worked differently between the two (regardless of HD or SD).

Basically, I just ran it to check if you had access to the Female Death Knight model. It exists both in SD and HD (and important distinction, since trying to run a HD resource while running in SD mode will yield a null value even for Reforged users), but appears to be Reforged exclusive upon testing. The same could likely be accomplished with the Female Demon Hunter model.

Thanks for the base code, and I hope this helps someone :)
 

Attachments

  • ReforgedChecker.w3x
    18.3 KB · Views: 10

TriggerHappy

Spell Moderator
Level 37
Joined
Jun 23, 2007
Messages
4,027
Wasn't sure where to put this, but I took inspiration from what was done here to check to see if someone was running Reforged or not. The reason for this was primarily because I wanted to explore the possibility of running separate code for Reforged Users and Non-Reforged Users in the event of something that worked differently between the two (regardless of HD or SD).

Basically, I just ran it to check if you had access to the Female Death Knight model. It exists both in SD and HD (and important distinction, since trying to run a HD resource while running in SD mode will yield a null value even for Reforged users), but appears to be Reforged exclusive upon testing. The same could likely be accomplished with the Female Demon Hunter model.

Thanks for the base code, and I hope this helps someone :)

JASS:
function IsReforged takes nothing returns boolean
   return GetLocalizedString("REFORGED") != "REFORGED"
endfunction
 
Top