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 BugOkay, 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:
return h
return 0
endfunction
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.
- 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:
globals
endglobals
return h
return 0
endfunction
function Test takes nothing returns nothing
set test = H2I(
GetTriggerUnit())
//This will compile just fine since "unit" is a handle set test = H2I("My String") //Will NOT compile, strings aren't handles.
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, H2
I) Basically, once we define a return, the function will be considered a valid input for the returned type. For example:
endfunction
function Test takes nothing returns nothing
//This will compile just fine, since the "ReturnString" function returns a string-type.
//This will NOT compile, it returns a string, not a unit-type.
endfunction
Now:
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.
function ReturnGood
takes nothing returns unit return GetTriggerUnit()
//Compiles fine, since you state to return a unit and do just that. endfunction
function ReturnBad takes nothing returns nothing
return GetTriggerUnit()
//error! You declare that you "return nothing" but you return a unit. 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:
function CompiledFinePriorToPatch
takes nothing returns string endif
return "This compiles!" //now we return a string.
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.
return h //error! You returned the wrong type!
endfunction
But when we add the final return, it will become "okay" for the compiler, and they'll think it is just fine.
return h
return 0 //The compiler ignores "return h", and just focuses on this.
//hmm... 0 seems like an integer to me, so it works fine **
//** prior to patch 1.24
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 n
th 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:
return H2I(h)-0x100000 //or 1048576
//No longer works as of patch 1.24
//Thus, you'd really use "return GetHandleId(h)-0x100000", the new native.
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 VarsLocal 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:
if value==null then
else
endif
endfunction
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:
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.
return i
return null
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:
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:
globals
endglobals
endfunction
To type cast I2X, you'd use:
globals
endglobals
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:
return h
return 0
endfunction
With:
That will fix most of the systems.
The fix to CSCache and Handle Vars is this:
Faux Handle Vars & CSCacheIt 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:
globals
endglobals
function SetHandleHandle
takes agent subject,
string label,
agent value
returns nothing if(value==null) then
else
endif
endfunction
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:
return h
return 0
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
return i
return null
endfunction
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:
globals
endglobals
endfunction
endfunction
endfunction
endfunction
endfunction
endfunction
//function Int2Terraindeformation takes integer i returns terraindeformation
// call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
// return LoadGroupHandle(MyHash,0,0)
//endfunction
//Lol there is no save/load for terrain deformations, so we are stuck.
//There is another way to typecast, known as the "Return Nothing" bug [discovered by Deaod]
//However, it no longer works.
endfunction
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:
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-HandleAfter 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.