• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Detect Game Version (GetPatchLevel)

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: 208
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
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...
 
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 5
Joined
Sep 6, 2010
Messages
91
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)
 
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: 148
Level 26
Joined
Aug 18, 2009
Messages
4,097
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 26
Joined
Aug 18, 2009
Messages
4,097
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 26
Joined
Aug 18, 2009
Messages
4,097
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.
 
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 14
Joined
Oct 19, 2014
Messages
187
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 26
Joined
Aug 18, 2009
Messages
4,097
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.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
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.
 
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
353
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 35
Joined
Feb 5, 2009
Messages
4,552
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: 41
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
 
Between all of this, what is most reliable to detect 1.32?

JASS:
function IsReforged takes nothing returns boolean
   return GetLocalizedString("REFORGED") != "REFORGED"
endfunction
Works in any warcraft 3 version, It only can fail when the user moded StringLists in the game files (unlikely) or when you call it before map init or whenever the game loads the default Stringlists.
 
GetLocalizedString returns text saved in the Stringtable for a key, or the key when nothing is found. The entry "REFORGED" is set in the file war3.w3mod:_locales\dede.w3mod:ui\framedef\globalstrings.fdf.
But the globalstring.fdf from V1.31 or older versions don't mention such a key, hence if you don't get the key back your map runs in Warcraft 3 V1.32+.

If the user is using SD/HD assets don't matter, as one only wants to know if the key is set at all.

While it is true that this technique and the new file system can be used to have different text based on asset mode which grants the ability to detect usage of SD/HD/Teen Anyway to detect when a player is using reforged or classic graphics?.
 
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
Do we have a reliable way of detecting game version 1.33?

Edit:
Can probably ask for if one of the new natives exists, if in Lua.
Lua:
function IsGameVersion133OrLater()
    if BlzGetUnitOrderCount then
        return true
    end
    return false
end

Sufficient for my own purpose.

This library is meant to run on all versions of WC3, older and newer, and because of that, it must be written in JASS, so this Lua snippet can't be included in it. But if this is enough for you, you're fine.
 
Level 2
Joined
Apr 20, 2022
Messages
11
Forgive me necro-posting

like #31
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.


SO I have new code, only String

JASS:
    local string tmp1 = GetLocalizedString("IGNORE")                 // IGNORE didn't exist until 1.28
    local string tmp2 = GetLocalizedString("DOWNLOADING_MAP")        // DOWNLOADING_MAP didn't exist until 1.30.2
    local string tmp3 = GetLocalizedString("UPDATE_REQUIRED")        // UPDATE_REQUIRED didn't exist until 1.30.0
    local string tmp4 = GetLocalizedString("WINDOW_MODE_WINDOWED")   // WINDOW_MODE_WINDOWED didn't exist until 1.31
    local string tmp5 = GetLocalizedString("REFORGED")               // REFORGED didn't exist until 1.32
    local string ver 
    if (JASS_MAX_ARRAY_SIZE <= 8192) then      // The array size limit was increased in 1.29, so if it's the same
        if tmp1 == "IGNORE" then
            set ver = "127"  // 1.27b or lower , 不再判断具体版本,有需要请自行深化
        else
            set ver = "128"  // 1.28 , 1.28a~1.28f
        endif
    elseif tmp2 == "DOWNLOADING_MAP"  then
        if tmp3 == "UPDATE_REQUIRED" then
          set ver = "129"
        else
          set ver = "130"  // 1.30.0 or 1.30.1
        endif
    elseif tmp4 == "WINDOW_MODE_WINDOWED" then 
        set ver = "130.2"  // 1.30.2, 1.30.3, 1.30.4
    elseif tmp5 == "REFORGED" then
        set ver = "131" // 1.31
    else
        set ver = "132" // 1.32 or higher ,重置版 ,战网国际欢迎你
    endif
    set tmp1 = null
    set tmp2 = null
    set tmp3 = null
    set tmp4 = null
    set tmp5 = null
    call DisplayTextToForce( GetPlayersAll(),"当前版本为:" + ver)
    set ver = null
 
Top