- Joined
- Nov 11, 2006
- Messages
- 7,647
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:
JASS:
function H2I takes handle h returns integer
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.
JASS:
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 forstring
,real
,integer
,boolean
, andcode
. By inputting a handle, you may input anything besides the types listed above, for example:
JASS:globals integer test endglobals function H2I takes handle h returns integer 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
- 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:
JASS:function ReturnString takes unit u returns string return GetUnitName(u)+"Whee" endfunction function Test takes nothing returns nothing local string s = ReturnString(GetTriggerUnit()) //This will compile just fine, since the "ReturnString" function returns a string-type. local unit u = ReturnString(GetTriggerUnit()) //This will NOT compile, it returns a string, not a unit-type. endfunction
Now:
JASS:
return h
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.
JASS:
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:
JASS:
function CompiledFinePriorToPatch takes nothing returns string
set bj_lastCreatedUnit = CreateUnit(Player(15),'hfoo',0,0,0)
if GetUnitUserData(bj_lastCreatedUnit)==50 then
return bj_lastCreatedUnit //we return a unit instead of a 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.
JASS:
function H2I takes handle h returns 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.
JASS:
function H2I takes handle h returns integer
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 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:
JASS:
function GetHandleIdPosition takes handle h returns integer
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 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:
JASS:
function SetHandleHandle takes handle subject, string name, handle value returns nothing
if value==null then
call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
else
call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
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:
JASS:
function GetHandleHandle takes handle subject, string name returns handle
return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
return null
endfunction
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.
JASS:
function I2H takes integer i returns handle
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:
JASS:
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:
JASS:
globals
hashtable MyHash = InitHashtable()
endglobals
function Widget2Unit takes widget w returns unit
call SaveWidgetHandle(MyHash,0,0,w)
return LoadUnitHandle(MyHash,0,0)
endfunction
To type cast I2X, you'd use:
JASS:
globals
hashtable MyHash = InitHashtable()
endglobals
function Integer2Widget takes integer int returns widget
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(int))
return LoadWidgetHandle(MyHash,0,0)
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:
Before I get into fixing these problems manually, I recommend giving this automated-tool a shot:
Handlevars Return Casting Converter
If that doesn't work, try some of the workarounds mentioned below!
Okay, let's get to one major fix that will probably knock off a lot of them.
Replace:
JASS:
function H2I takes handle h returns integer
return h
return 0
endfunction
JASS:
function H2I takes handle h returns integer
return GetHandleId(h)
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:
JASS:
globals
hashtable ht
endglobals
function SetHandleHandle takes agent subject, string label, agent value returns nothing
if(value==null) then
call RemoveSavedHandle( ht, GetHandleId(subject), StringHash(label))
else
call SaveAgentHandle( ht, GetHandleId(subject), StringHash(label), value)
endif
endfunction
function GetHandleUnit takes agent subject, string label returns unit
return LoadUnitHandle( ht, GetHandleId(subject), StringHash(label))
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:
JASS:
function Handle2Int takes handle h returns integer
return h
return 0
endfunction
function Int2Group takes integer i returns group
return i
return null
endfunction
function Int2Unit takes integer i returns unit
return i
return null
endfunction
function Int2Location takes integer i returns location
return i
return null
endfunction
function Int2Effect takes integer i returns effect
return i
return null
endfunction
function Int2Timer takes integer i returns timer
return i
return null
endfunction
function Int2Terraindeformation takes integer i returns terraindeformation
return i
return null
endfunction
function Int2Destructable takes integer i returns destructable
return i
return null
endfunction
function Int2Trigger takes integer i returns trigger
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:
JASS:
globals
hashtable MyHash = InitHashtable()
endglobals
function Handle2Int takes handle h returns integer
return GetHandleId(h)
endfunction
function Int2Group takes integer i returns group
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadGroupHandle(MyHash,0,0)
endfunction
function Int2Unit takes integer i returns unit
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadUnitHandle(MyHash,0,0)
endfunction
function Int2Location takes integer i returns location
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadLocationHandle(MyHash,0,0)
endfunction
function Int2Effect takes integer i returns effect
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadEffectHandle(MyHash,0,0)
endfunction
function Int2Timer takes integer i returns timer
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadTimerHandle(MyHash,0,0)
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.
function Int2Destructable takes integer i returns destructable
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadDestructableHandle(MyHash,0,0)
endfunction
function Int2Trigger takes integer i returns trigger
call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
return LoadTriggerHandle(MyHash,0,0)
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:
JASS:
real
string
integer
boolean
player
widget
destructable
item
unit
ability
timer
trigger
triggercondition
triggeraction
event
force
group
location
rect
boolexpr
sound
effect
unitpool
itempool
quest
questitem
defeatcondition
timerdialog
leaderboard
multiboard
multiboarditem
trackable
dialog
button
texttag
lightning
image
ubersplat
region
fogstate
fogmodifier
agent
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 edited: