vJASS & Zinc Documentation
For the latest documentation about how it works vJASS and Zinc language layers for Warcraft III, please follow these links:
Jasshelper documentation - Zinc documentation - WC3 Optimizer documentation

Make a Map Work for Patch 1.24

moyack · 633

0 Members and 1 Guest are viewing this topic.

Make a Map Work for Patch 1.24
on: November 20, 2010, 09:02:35 PM

Related Topics or Resources



Patch 1.24 Compatibility

Table of Contents:


Introduction:

Once patch 1.24 was introduced, many maps have died and no longer work for patch 1.24+. Those maps used a particular bug known as the "return bug", which allowed you to typecast, or change the entity of one data type to another, using a simple method.

Why was it fixed?

Basically, people were toying around with WE a lot. Enough to use I2C and C2I by utilizing the return bug to allow bytecode to be executed in JASS. Then hackers would exploit this "feature" to execute bytecode in JASS to open the command prompt and implement a virus. Luckily though, bytecode shouldn't work anymore since wc3 apparently doesn't let arrays be fired as code anymore.

Typecasting - The Return Bug

Okay, well I talked a lot about this mysterious "Return Bug". What is it? It was a bug that functioned prior to patch 1.24. The key function that made the return bug useful was this:
Code: jass
  1. function H2I takes handle h returns integer
  2.     return h
  3.     return 0
  4. endfunction
  5.  

This famous bug was first exploited by SuperIKI followed by Peppar, and then was used in general in many systems from then on.

What does it do?

Well, it used to typecast variables. It would allow you to essentially transform the entity of the handle input into an integer. This allowed you to get the internal handle ID of that handle you've input, which is generally useful since it is unique for all but a few handles with different allocation methods. (eg: texttags/ubersplats) Now let me break it down to mention the key parts.

Code: jass
  1. function H2I takes handle h returns integer
  • H2I - This is the function's name, and refers to "Handle to Integer", following the same naming convention as the other X2Y functions that blizzard has. (I2S, R2I, etc.)
  • handle h - A handle is a data type which many of the types defined in wc3 will extend. Any type (example: unit, location, group) is a handle except for string, real, integer, boolean, and code. By inputting a handle, you may input anything besides the types listed above, for example:
Code: jass
  1. globals
  2.     integer test
  3. endglobals
  4. function H2I takes handle h returns integer
  5.     return h
  6.     return 0
  7. endfunction
  8. function Test takes nothing returns nothing
  9.     set test = H2I(GetTriggerUnit()) //This will compile just fine since "unit" is a handle
  10.     set test = H2I("My String")      //Will NOT compile, strings aren't handles.
  11. endfunction
[/li]
[li]returns integer - Okay, so far we take in handle h, now to complete the H2I cycle, we must return an integer. (Hence, H2I) Basically, once we define a return, the function will be considered a valid input for the returned type. For example:
Code: jass
  1. function ReturnString takes unit u returns string
  2.     return GetUnitName(u)+"Whee"
  3. endfunction
  4. function Test takes nothing returns nothing
  5.     local string s = ReturnString(GetTriggerUnit())
  6.     //This will compile just fine, since the "ReturnString" function returns a string-type.
  7.     local unit u   = ReturnString(GetTriggerUnit())
  8.     //This will NOT compile, it returns a string, not a unit-type.
  9. endfunction
    [/li]

Now:
Code: jass
  1.     return h
  2.     return 0

return is the GUI equivalent to Skip Remaining Actions. Once you define a type after the return, that means the function also must return that type defined.
Code: jass
  1. function ReturnGood takes nothing returns unit
  2.     return GetTriggerUnit() //Compiles fine, since you state to return a unit and do just that.
  3. endfunction
  4. function ReturnBad takes nothing returns nothing
  5.     return GetTriggerUnit() //error! You declare that you "return nothing" but you return a unit.
  6. endfunction

The thing peculiar about return prior to patch 1.24 though, was that the compiler checked only the last return defined. So as long as you return the correct type at the end, the previous returns will work as well. Example:
Code: jass
  1. function CompiledFinePriorToPatch takes nothing returns string
  2.     set bj_lastCreatedUnit = CreateUnit(Player(15),'hfoo',0,0,0)
  3.     if GetUnitUserData(bj_lastCreatedUnit)==50 then
  4.         return bj_lastCreatedUnit //we return a unit instead of a string???!
  5.     endif
  6.     return "This compiles!" //now we return a string.
  7. endfunction

The compiler used to check only return "This compiles!" to see if it was returning the correct data type. Obviously, this is just an example that does absolutely nothing, but it just shows how it could be abused.

Now, let's go back to the return h/return 0. If we were to define only return h, it would show a compile error since it is supposed to return an integer.
Code: jass
  1. function H2I takes handle h returns integer
  2.     return h //error! You returned the wrong type!
  3. endfunction

But when we add the final return, it will become "okay" for the compiler, and they'll think it is just fine.
Code: jass
  1. function H2I takes handle h returns integer
  2.     return h
  3.     return 0 //The compiler ignores "return h", and just focuses on this.
  4. //hmm... 0 seems like an integer to me, so it works fine **
  5. //** prior to patch 1.24
  6. endfunction

The cool thing though, is that return 0 is never executed. It skips the remaining actions after the return h, so return 0 is never executed at all.

When doing this, it allowed us to successfully typecast from a handle to an integer. Now, if you were to return an integer of a handle, what would it return? Well, the way normally allocated handles work is that they have specific handle ids. When a new handle is declared, it will be given a new unique handle ID. The way they are assigned is by the showing that it is the nth handle declared. They go from order, from 0x100000 (or 1048576), and then it goes +1 for each new handle declared. So say you have a handle, you can check how many handles were declared before it by using this function:
Code: jass
  1. function GetHandleIdPosition takes handle h returns integer
  2.     return H2I(h)-0x100000 //or 1048576
  3. //No longer works as of patch 1.24
  4. //Thus, you'd really use "return GetHandleId(h)-0x100000", the new native.
  5. endfunction

Now, since the handles (besides texttags and ubersplats etc.) had unique ID's, this made it perfect for spells. And thus began the era of:
Local Handle Vars

Local Handle Vars was the most widely used system in JASS of that time, and relied solely on gamecache for timer attachment purposes. It uses H2I, but that wasn't the only part of the return bug it used. Let's first look at the function for attaching a handle:
Code: jass
  1. function SetHandleHandle takes handle subject, string name, handle value returns nothing
  2.     if value==null then
  3.         call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
  4.     else
  5.         call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
  6.     endif
  7. endfunction
  8.  

Ok, this stores an integer into LocalVars() [a defined gamecache]. Gamecache are much like hashtables, where they took a parent string name as a "category" and then the "name". It is analogous to a computer. We store files into a unique folder (represents the category) and store the child file (represents "name"). Then we assign the value. Now, since we couldn't manually assign a handle, we would assign the ID of the handle instead. It was a really neat way to attach objects, but you must think, "How do we get the data back"? Well, we use the same method pretty much:
Code: jass
  1. function GetHandleHandle takes handle subject, string name returns handle
  2.     return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
  3.     return null
  4. endfunction
  5.  

Now, it expects us to return a "handle". Since all handles can have a null value, we can return "null" at the end and it will suffice. Now, we return the integer, and it expects a handle to be returned. Well, there is an H2I, why not an I2H? Well, this is essentially the technique for an I2H.
Code: jass
  1. function I2H takes integer i returns handle
  2.     return i
  3.     return null
  4. endfunction

That is essentially the return bug wrapped up. So why are maps broken? Well, almost all JASS spells at the time used local handle vars or CSCache, which means that since the return bug no longer works, the functions no longer work. In fact, it will give errors now if you try to use this method, that is the point of this tutorial. To fix it.

Patch 1.24:

Both a killer and a revolutionary patch. In that patch, they removed the return bug, but added a great replacement:
Code: jass
  1. native GetHandleId takes handle h returns integer

This is the new H2I function, except it is a native so it is very fast in comparison. We don't have an I2H, but why need that when we have hashtables? Hashtables are the revolutionary feature of patch 1.24, which allows us to store the data types we need without being restricted to integers, strings, reals, and booleans.

However, hashtables also opened a new way to type-cast:
Code: jass
  1. globals
  2.     hashtable MyHash = InitHashtable()  
  3. endglobals
  4. function Widget2Unit takes widget w returns unit
  5.     call SaveWidgetHandle(MyHash,0,0,w)
  6.     return LoadUnitHandle(MyHash,0,0)
  7. endfunction

To type cast I2X, you'd use:
Code: jass
  1. globals
  2.     hashtable MyHash = InitHashtable()
  3. endglobals
  4. function Integer2Widget takes integer int returns widget
  5.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(int))
  6.     return LoadWidgetHandle(MyHash,0,0)
  7. endfunction

It uses fogstates and basically exploits hashtables, but this isn't very useful any more now that we have the glorious hashtables to replace the older systems. (Only W2U is useful in specific cases)

But that isn't the thing. We want to know how to fix maps that used the old systems. But with this knowledge (don't worry, it isn't pointless) you'll be able to do that easily.

Fixing an Old Map:

Hooray, we finally get to fix an old map!

Ok, now as we have discussed, there are a few systems that cause this:
  • CSCache/Caster System
  • Local Handle Vars
  • ABCt - (Resolve: Use TimerTicker or a different timer system)
  • TimerTicker - (Use the compatibility version)
  • Old versions of Table (included with CSCache)
  • Cinematic System v1.5 or lower (Resolve: Use v1.5b)
  • HAIL
  • Graphics System
  • Decal
  • T2Ix
  • Unit Get Local (Deprecated Local Handle Vars)
  • Class System
  • Spell Classes System
  • Handle Lists
  • HSAS
  • Special Events/Alternative Spell Templates System

There are more, but that gets a lot of them. All but the ones with a "Resolve" use H2I and don't have an upgrade. (Except for CSCache/Caster System/Table but those use vJASS, so you might not want to use it)

Fixes:

Ok, one big major fix that will probably knock off a lot of them.

Replace:
Code: jass
  1. function H2I takes handle h returns integer
  2.     return h
  3.     return 0
  4. endfunction
With:
Code: jass
  1. function H2I takes handle h returns integer
  2.     return GetHandleId(h)
  3. endfunction

That will fix most of the systems.

The fix to CSCache and Handle Vars is this:
Faux Handle Vars & CSCache

It uses hashtables to store everything instead of gamecache, and just uses GetHandleId() to replace it, and uses the hashtable API for everything else. It allows you to use the same naming convention as the previous local handle vars & CSCache so you don't have to make changes later.

Let's observe some of the functions:
Code: jass
  1.     globals
  2.         hashtable ht
  3.     endglobals
  4.     function SetHandleHandle takes agent subject,  string label, agent value returns nothing
  5.         if(value==null) then
  6.             call RemoveSavedHandle( ht, GetHandleId(subject), StringHash(label))
  7.         else
  8.             call SaveAgentHandle( ht, GetHandleId(subject), StringHash(label), value)
  9.         endif
  10.     endfunction
  11.     function GetHandleUnit takes agent subject, string label returns unit
  12.         return LoadUnitHandle( ht, GetHandleId(subject), StringHash(label))
  13.     endfunction

What this does is it saves an agent handle (the ones we use/create, essentially) and then the 2nd function returns a unit that was saved. Basically, they use GetHandleId() instead of H2I(), and they use StringHash() to convert it an integer input for the hashtable functions.

To implement Faux HandleVars & CSCache, copy the respective library, and paste it into the map's header. (The map header is found by opening the trigger editor, and clicking on the map's name listed in the left window) Then it should say "Custom Script Code" and just replace the old handle vars/CScache with the new ones.

Now it is all fine for CSCache and Handle Vars, but some maps might use things like the Class System, or generally systems that use I2X.

Let's look at some functions of the Class System by AIAndy:
Code: jass
  1. function Handle2Int takes handle h returns integer
  2.   return h
  3.   return 0
  4. endfunction
  5.  
  6. function Int2Group takes integer i returns group
  7.   return i
  8.   return null
  9. endfunction
  10.  
  11. function Int2Unit takes integer i returns unit
  12.   return i
  13.   return null
  14. endfunction
  15.  
  16. function Int2Location takes integer i returns location
  17.   return i
  18.   return null
  19. endfunction
  20.  
  21. function Int2Effect takes integer i returns effect
  22.   return i
  23.   return null
  24. endfunction
  25.  
  26. function Int2Timer takes integer i returns timer
  27.   return i
  28.   return null
  29. endfunction
  30.  
  31. function Int2Terraindeformation takes integer i returns terraindeformation
  32.   return i
  33.   return null
  34. endfunction
  35.  
  36. function Int2Destructable takes integer i returns destructable
  37.   return i
  38.   return null
  39. endfunction
  40.  
  41. function Int2Trigger takes integer i returns trigger
  42.   return i
  43.   return null
  44. endfunction
  45.  

How do you think we should fix this? Well, if you look above, you'll realize we can now use fogstates along with hashtables for I2X functions. Thus, we'd end up getting this:
Code: jass
  1. globals
  2.     hashtable MyHash = InitHashtable()
  3. endglobals
  4.  
  5. function Handle2Int takes handle h returns integer
  6.     return GetHandleId(h)
  7. endfunction
  8.  
  9. function Int2Group takes integer i returns group
  10.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  11.     return LoadGroupHandle(MyHash,0,0)
  12. endfunction
  13.  
  14. function Int2Unit takes integer i returns unit
  15.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  16.     return LoadUnitHandle(MyHash,0,0)
  17. endfunction
  18.  
  19. function Int2Location takes integer i returns location
  20.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  21.     return LoadLocationHandle(MyHash,0,0)
  22. endfunction
  23.  
  24. function Int2Effect takes integer i returns effect
  25.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  26.     return LoadEffectHandle(MyHash,0,0)
  27. endfunction
  28.  
  29. function Int2Timer takes integer i returns timer
  30.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  31.     return LoadTimerHandle(MyHash,0,0)
  32. endfunction
  33.  
  34. //function Int2Terraindeformation takes integer i returns terraindeformation
  35. //    call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  36. //    return LoadGroupHandle(MyHash,0,0)
  37. //endfunction
  38. //Lol there is no save/load for terrain deformations, so we are stuck.
  39. //There is another way to typecast, known as the "Return Nothing" bug [discovered by Deaod]
  40. //However, it no longer works.
  41.  
  42. function Int2Destructable takes integer i returns destructable
  43.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  44.     return LoadDestructableHandle(MyHash,0,0)
  45. endfunction
  46.  
  47. function Int2Trigger takes integer i returns trigger
  48.     call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
  49.     return LoadTriggerHandle(MyHash,0,0)
  50. endfunction

Basically, we've succesfully completed it for all but the terrain deformation. Sadly, that one no longer works. There are a handful of types we can't save, but we don't use it that much anyway. The only real reason to save a terraindeformation is to stop it anyway, so you'd need to manually fix that or just generally remove the functions. The only types that support typecasting at the moment are:
Code: jass
  1. real
  2. string
  3. integer
  4. boolean
  5. player
  6. widget
  7. destructable
  8. item
  9. unit
  10. ability
  11. timer
  12. trigger
  13. triggercondition
  14. triggeraction
  15. event
  16. force
  17. group
  18. location
  19. rect
  20. boolexpr
  21. sound
  22. effect
  23. unitpool
  24. itempool
  25. quest
  26. questitem
  27. defeatcondition
  28. timerdialog
  29. leaderboard
  30. multiboard
  31. multiboarditem
  32. trackable
  33. dialog
  34. button
  35. texttag
  36. lightning
  37. image
  38. ubersplat
  39. region
  40. fogstate
  41. fogmodifier
  42. agent
  43. hashtable

The rest can't since there are no hashtable functions for them. However, this system can help fix that problem, for most of them (except weathereffect and terraindeformation):
http://www.thehelper.net/forums/showthread.php/152826-Handle

After you have fixed the functions, and your map saves, you may still experience some problems. Perhaps your map will throw an immediate error upon testing the map or something along those lines. Now that the map can be saved, you will need to troubleshoot the problem by disabling triggers until your map loads. Once your map loads, hopefully you will be able to narrow down which trigger caused the problem. In general, it may be harder to fix that trigger opposed to just remaking it, so I recommend that you remake whatever does not function properly.

Conclusion:

Hopefully you may now fix your map. Generally, Faux Handle Vars & CSCache will usually fix a map with ease, but sometimes it isn't enough if they use special systems. Generally, it is just good to know how to fix them, since it will help you to refrain from using the old systems as well.

Credits:
  • SuperIKI and Peppar and others for H2I.
  • Vexorian for Faux Handle Vars and CSCache
  • Jesus4Lyf for some of the malicious bytecode info
  • AIAndy for making a system that can no longer work in 1.24. =P (since the typecasting methods don't work for terraindeformations)
  • kingkingyyk3 for a quick reference to the X2Y and I2X functions.
« Last Edit: December 23, 2017, 07:03:51 PM by moyack »



 

Power of Corruption - A Warcraft III altered melee map   Chaos Realm - The world of Game modders and wc3 addicts     WC3JASS.com - The JASS Vault   Jetcraft - A Starcraft II mod