How to build class.object references - XCOM:EU 2012
Contents
Overview
I've been mucking about with the hex code enough that some of it is starting to make sense.
Even the constructions that look something like : m_kSoldier.m_kSoldier.kClass.eType
They actually follow some pretty straightforward rules which I thought I'd share.
Programs and Tools
See these articles:
Details
There are two types of "dot" constructions, class/object and struct.
- Class/object uses the 0x19 context token
- Struct uses the 0x35 struct token
the 0x19 context token
Suppose I wanted to build something that looked like:
<object1>.<object2>
The breakdown looks like so:
- 19 -- context token
- ## ... ## -- reference to <object1>
- ## ## -- two bytes (little endian order) representing the virtual size of <object2>
- ## ## ## ## -- the return value of <object2)
- 00 -- this is always zero ... not sure if it can fill any other role
- ## ... ## -- reference to <object2>
Each object can be either a class variable or a class function.
- A class variable has the structure : 01 ## ## ## ##
- A class function usually has the structure : 1B ## ## ## ## 00 00 00 00 <parameters> 16
Sometimes a function may be a "final" function: then it has the structure '1C ## ## ## ## <parameters> 16'.
For a class variable, the return value is the same as the object reference itself.
For a class function, the return value is ... the return value. If the value has no return value it is set to '00 00 00 00'
Example1:
- m_iFoo : 01 44 32 00 00
- m_iBar : 01 87 21 00 00
- m_iFoo.m_iBar
- 19 -- context token
- 01 44 32 00 00 -- reference to class variable m_iFoo
- 09 00 -- virtual size of m_iBar (5 file + 4 additional virtual bytes)
- 87 21 00 00 -- return value of m_iBar (the base reference without 01 class var token)
- 00 -- just a zero
- 01 87 21 00 -- reference to class variable m_iBar
Example2:
- m_iFoo : 01 44 32 00 00
- Bar( ) : 1B 99 25 00 00 00 00 00 00 16
- Bar ReturnValue : 3E 77 00 00
- iSnafu : 00 D4 AA 00 00
- m_iFoo.Bar(iSnafu)
- 19 -- context token
- 01 44 32 00 00 -- class variable m_iFoo
- 13 00 -- size of Bar(iSnafu)
- 3E 77 00 00 -- return value Bar
- 00 -- just a zero
- 1B 99 25 00 00 00 00 00 00 00 D4 AA 00 00 16
Larger Constructs
Larger constructs are daisy-chained together in a similar manner.
<object1>.<object2>.<object3> has the form:
- 19 -- context token
- 19 -- context token
- ## ... ## -- reference to <object1>
- ## ## -- size of <object2>
- ## ## ## ## -- return value of <object2>
- 00
- ## ... ## -- reference to <object2>
- ## ## -- size of <object3>
- ## ## ## ## -- return value of <object3>
- 00
- ## ... ## -- reference to <object3>
All of the context tokens go at the beginning of the construction. There will be one 0x19 token for each "dot" in the construction (if all members are CLASS objects -- structs follow different rules and are built in the opposite order [see section Example]).
The return values and reference can be looked up in various places within UE explorer without having to dig through the hex viewer.
Example
Here's an example of some code I'm working on now. The original line's breakdown is:
kLockerItem.bClassLocked = !STORAGE().IsClassEquippable(kSoldier.GetClass(), byte(kItem.iItem)); 14 -- Boolean LET 2D -- boolean token 35 A7 2C 00 00 AB 2C 00 00 00 01 -- struct bClassLocked 00 FD 2C 00 00 -- local variable kLockerItem 81 -- NOT 19 -- class context token 1B 23 27 00 00 00 00 00 00 16 -- STORAGE 47 00 -- size of next context (IsClassEquippable(...)) 76 40 00 00 -- return value next context 00 1B 33 14 00 00 00 00 00 00 -- IsClassEquippable( 19 -- context token 00 FF 2C 00 00 -- local variable kSoldier 0A 00 -- size of next context (GetClass()) 91 45 00 00 -- return value next context (GetClass()) 00 1B B5 0E 00 00 00 00 00 00 16 -- GetClass() 38 3D -- int-to-byte conversion 35 C5 02 00 00 C8 02 00 00 00 00 48 -- struct iItem 00 2D 00 00 -- local variable kItem 16 -- execute IsClassEquippable 16 -- execute NOT
My goal is to build a new line:
- kLockerItem.bClassLocked = !STORAGE().IsClassEquippable(kSoldier.m_kSoldier.kClass.eWeaponType, byte(kItem.iItem));
From elsewhere in the code I already have the construction:
- m_kSoldier.kClass.eWeaponType
- 35 2F FF FF FF 7D FA FF FF 00 00 35 B4 F9 FF FF 74 FA FF FF 00 01 01 EC 44 00 00
The thing to note here is that structures are built in the opposite order to objects.
So:
- 35 -- struct token
- 2F FF FF FF 7D FA FF FF 00 00 -- eWeaponType reference
- 35 -- struct token
- B4 F9 FF FF 74 FA FF FF 00 01 -- kClass reference
- 01 -- class variable token
- EC 44 00 00 -- m_kSoldier reference
The first step is to build kSoldier.m_kSoldier.kClass.eWeaponType.
The order is 35 <eWeaponType> 35 <kClass> 19 <kSoldier> <m_kSoldier>
The full breakdown is:
- kSoldier.m_kSoldier.kClass.eWeaponType
- 35 -- struct token
- 2F FF FF FF 7D FA FF FF 00 00 -- eWeaponType
- 35 -- struct token
- B4 F9 FF FF 74 FA FF FF 00 01 -- kClass
- 19 -- context token
- 00 FF 2C 00 00 -- local variable kSoldier
- 09 00 -- size of next context (m_kSoldier)
- EC 44 00 00 -- return value next context (m_kSoldier)
- 00
- 01 EC 44 00 00 -- class var m_kSoldier
With that in hand I substitute it for the original kSoldier.GetClass() parameter in IsClassEquippable().
This yields:
kLockerItem.bClassLocked = !STORAGE().IsClassEquippable(kSoldier.m_kSoldier.kClass.eWeaponType, byte(kItem.iItem)); 14 -- Boolean LET 2D -- boolean token 35 A7 2C 00 00 AB 2C 00 00 00 01 -- struct bClassLocked 00 FD 2C 00 00 -- local variable kLockerItem 81 -- NOT 19 -- class context token 1B 23 27 00 00 00 00 00 00 16 -- STORAGE 47 00 -- size of next context (IsClassEquippable(...)) 76 40 00 00 -- return value next context 00 1B 33 14 00 00 00 00 00 00 -- IsClassEquippable( 35 -- struct token 2F FF FF FF 7D FA FF FF 00 00 -- eWeaponType 35 -- struct token B4 F9 FF FF 74 FA FF FF 00 01 -- kClass 19 -- context token 00 FF 2C 00 00 -- local variable kSoldier 09 00 -- size of next context (m_kSoldier) EC 44 00 00 -- return value next context (m_kSoldier) 00 01 EC 44 00 00 -- class var m_kSoldier 38 3D -- int-to-byte conversion 35 C5 02 00 00 C8 02 00 00 00 00 48 -- struct iItem 00 2D 00 00 -- local variable kItem 16 -- execute IsClassEquippable 16 -- execute NOT
However, this hex code will not run like this. Changing the parameter to IsClassEquippable changed the overall virtual size of the IsClassEquippable context that follows STORAGE(). Specifically the '47 00' size has to be updated to reflect the larger parameter size.
So how to figure out the new virtual size? It can be done by hand, but it's much easier to let UE Explorer do the heavy lifting here. In a similar manner to how I put in placeholder value for jumps, the incorrect value will still decompile correctly. This allows me to use the Token view to figure out the new virtual size of the context.
The first step is to verify that the new line decompiles properly, which it does:
- kLockerItem.bClassLocked = !STORAGE().IsClassEquippable(kSoldier.m_kSoldier.kClass.eWeaponType, byte(kItem.iItem));
The next step is to correct the context virtual size. To do this enter the Token view and find the line of code:
(0x1D8) LetBool(162) -> BoolVariable(29) -> StructMember(28) -> LocalVariable(9) -> NativeFunction(132) -> Context(130) -> VirtualFunction(10) -> EndFunctionParms(1) -> VirtualFunction(108) -> StructMember(68) -> StructMember(49) -> Context(30) -> LocalVariable(9) -> InstanceVariable(9) -> IntToByte(30) -> StructMember(28) -> OutVariable(9) -> EndFunctionParms(1) -> EndFunctionParms(1)
- kLockerItem.bClassLocked = !STORAGE().IsClassEquippable(kSoldier.m_kSoldier.kClass.eWeaponType, byte(kItem.iItem))
The IsClassEquippable context is the VirtualFunction(108) reference. 108 is in decimal, so convert to hex = 0x6C
This means that the '47 00' size in the context construction needs to be replaced with '6C 00'.
With this change the new construction decompiles and runs correctly.
Notes:
- If the virtual size is incorrect UE Explorer will decompile the object correctly but the program will crash upon execution. I often use the token view in UE Explorer to help find and correct the virtual sizes in these contexts
- If the Return Value is incorrect the program may crash or simply not return the correct value depending on where in the construction the incorrect value is located.
- Until recently it has been difficult to find an instance where declaring an integer constant using 24 or 2C made an noticeable difference.
- Some people have been the habit of using 2C tokens as visually they seem easier to pick out than the 24 tokens.
- The original construction was:
- if (eItem == 3)
- 07 88 00 9A 38 3A 00 77 40 00 00 38 3A 24 03 16
- The == operator turns out to only be valid for integers, not for bytes, which is why eItem has a '38 3A' byte-to-int token pair in front of it.
- Interestingly, the 3 value '24 03' also had a '38 3A' byte-to-int token pair.
- Changing the hex to:
- if (eItem == 3)
- 07 88 00 9A 38 3A 00 77 40 00 00 38 3A 2C 03 16
- the game would crash-to-desktop when the code was executed. Reverting it back to '24 03' solved the issue.
- This is, at it's core, an operator typecasting issue. See article How to understand Unreal Typecasting.
References
Referred to by this article:
- Help:Managing_files
- Modding_Tools_-_XCOM:EU_2012
- Modding_XCOM:EU_2012
- How to understand Unreal Typecasting
That refer to this article: