How to build class.object references - XCOM:EU 2012

From Nexus Mods Wiki
Revision as of 00:12, 16 November 2018 by Dubiousintent (talk | contribs) (Added 'Category:Mod_Creation')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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:

  1. 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
  2. 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.
  3. 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:



That refer to this article: