Enum Expansion - XCOM:EU 2012
Overview
This article is derived from discoveries in the UPK file format thread on the Nexus XCOM Mod Talk forum. See also the article Enums, from which the following is quoted:
An enum (also enumerated type or enumeration) is a primitive data type in UnrealScript. The possible values of an enum type are a list of identifiers, which can be compared not only for equality, but also for order. Enumerated types are always stored as 8-bit byte values.
Expanding an enumerated type can become necessary because the game engine tends to clamp variables to live within the defined enum range. This means you can either replace existing enumerated types or you have to expand the enum to increase the list of variables.
For examples of other enumerated types, see XGTacticalGameCoreData.EProperties.
Programs and Tools
Details
The motivation for development of this method came from Long War EW: where we plan to expand the number of UFO types from the vanilla 9 to 14. The new UFOs will share graphics and maps with 5 of the existing UFOs, but will have different stats (for example, a Scout and a Fighter will have different stats in the air, but have the same hull and use the same maps). Unfortunately, we found that when attempting to create AI objectives using the new ship enums that the value would be clamped to within the given range defined by XGGameData.EShipType:
enum EShipType { eShip_None, eShip_Interceptor, eShip_Skyranger, eShip_Firestorm, eShip_UFOSmallScout, eShip_UFOLargeScout, eShip_UFOAbductor, eShip_UFOSupply, eShip_UFOBattle, eShip_UFOEthereal, eShip_MAX };
The format of an enum is actually quite simple. It begins with an enum header consisting of 20 bytes (functions begin with a 48 byte header). The only one that needs changing is the 5th word which is the length of the enum.
For EShipType, this is:
7C 2E 00 00 76 5F 00 00 00 00 00 00 7C 2E 00 00 0B 00 00 00 // length
Following this header is a variable size list (of the defined length) of 8-byte namelist references. For EShipType, this consists of :
// Comments added 54 2D 00 00 00 00 00 00 // eShip_None 52 2D 00 00 00 00 00 00 // eShip_Interceptor 55 2D 00 00 00 00 00 00 // eShip_Skyranger 51 2D 00 00 00 00 00 00 // eShip_Firestorm 5A 2D 00 00 00 00 00 00 // eShip_UFOSmallScout 59 2D 00 00 00 00 00 00 // eShip_UFOLargeScout 56 2D 00 00 00 00 00 00 // eShip_UFOAbductor 5B 2D 00 00 00 00 00 00 // eShip_UFOSupply 57 2D 00 00 00 00 00 00 // eShip_UFOBattle 58 2D 00 00 00 00 00 00 // eShip_UFOEthereal 53 2D 00 00 00 00 00 00 // eShip_MAX
It's also possible to use the same name multiple times within the same enum. The following vanilla XGGameData.ENumPlayers enum does just this:
enum ENumPlayers { ENumPlayers, ENumPlayers, ENumPlayers, ENumPlayers, ENumPlayers_MAX };
using hex:
80 AE 00 00 76 5F 00 00 00 00 00 00 BF 61 00 00 05 00 00 00 // length 01 2A 00 00 01 00 00 00 // ENumPlayers (1) 01 2A 00 00 02 00 00 00 // ENumPlayers (2) 01 2A 00 00 03 00 00 00 // ENumPlayers (3) 01 2A 00 00 04 00 00 00 // ENumPlayers (4) 02 2A 00 00 00 00 00 00 // ENumPlayers_MAX
Note that the upper 4 bytes of the name reference now contain non-zero data, indicating that the name index isn't an 8 byte long, but instead a composite of 2 4byte ints.
In this case the enum values use the same name as the enumeration itself -- ENumPlayers.
To alter the enum, simply resize the function (using exactly the same methods as to increase the size of a function: see Hex editing UPK files), and insert additional 8-byte namelist references, as well as changing the length to match :
54 2D 00 00 00 00 00 00 // eShip_None 52 2D 00 00 00 00 00 00 // eShip_Interceptor 55 2D 00 00 00 00 00 00 // eShip_Skyranger 51 2D 00 00 00 00 00 00 // eShip_Firestorm 5A 2D 00 00 00 00 00 00 // eShip_UFOSmallScout 59 2D 00 00 00 00 00 00 // eShip_UFOLargeScout 56 2D 00 00 00 00 00 00 // eShip_UFOAbductor 5B 2D 00 00 00 00 00 00 // eShip_UFOSupply 57 2D 00 00 00 00 00 00 // eShip_UFOBattle 58 2D 00 00 00 00 00 00 // eShip_UFOEthereal 6F 29 00 00 00 00 00 00 // eMPT_SniperSquaddie // +8 // stand-in for FIGHTER 5F 29 00 00 00 00 00 00 // eMPT_HeavySquaddie // +8 // stand-in for RAIDER 73 29 00 00 00 00 00 00 // eMPT_SupportSquaddie // +8 // stand-in for HARVESTER 5B 29 00 00 00 00 00 00 // eMPT_AssaultSquaddie // +8 // stand-in for ASSAULT CARRIER 5A 29 00 00 00 00 00 00 // eMPT_AssaultColonel // +8 // stand-in for TERROR SHIP 53 2D 00 00 00 00 00 00 // eShip_MAX
It is possible to resize the UPK and insert additional names, but in very few cases will this matter, so we've chosen here to use some name entries from the multiplayer template enum.
An example of where it might matter is if inserting additional items, as a byte-to-string cast (hex bytes 38 52) will retrieve the defined value of the enum instead of converting it to a number.
After resizing and inserting the new code, UE Explorer decompiles the enum EShipType as:
enum EShipType { eShip_None, eShip_Interceptor, eShip_Skyranger, eShip_Firestorm, eShip_UFOSmallScout, eShip_UFOLargeScout, eShip_UFOAbductor, eShip_UFOSupply, eShip_UFOBattle, eShip_UFOEthereal, eMPT_SniperSquaddie, eMPT_HeavySquaddie, eMPT_SupportSquaddie, eMPT_AssaultSquaddie, eMPT_AssaultColonel, eShip_MAX };
The game still runs normally after the above change. Further, the "clamping" that we had been experiencing has been resolved.
Separate Content
UPK_Mod file used with UPK-Modder to apply Increase number of EShip enums to the XComGame.UPK.
MODFILEVERSION=4 UPKFILE=XComGame.upk GUID=5B 06 B8 18 67 22 12 44 85 9B A8 5B 9D 57 1D 4B // EW Patch 1 FUNCTION=EShipType@XGGameData RESIZE=28 // // increase number of EShip enums // [BEFORE_HEX] [HEADER] 7C 2E 00 00 76 5F 00 00 00 00 00 00 7C 2E 00 00 0B 00 00 00 // length [/HEADER] 54 2D 00 00 00 00 00 00 // eShip_None 52 2D 00 00 00 00 00 00 // eShip_Interceptor 55 2D 00 00 00 00 00 00 // eShip_Skyranger 51 2D 00 00 00 00 00 00 // eShip_Firestorm 5A 2D 00 00 00 00 00 00 // eShip_UFOSmallScout 59 2D 00 00 00 00 00 00 // eShip_UFOLargeScout 56 2D 00 00 00 00 00 00 // eShip_UFOAbductor 5B 2D 00 00 00 00 00 00 // eShip_UFOSupply 57 2D 00 00 00 00 00 00 // eShip_UFOBattle 58 2D 00 00 00 00 00 00 // eShip_UFOEthereal 53 2D 00 00 00 00 00 00 // eShip_MAX [/BEFORE_HEX] // // [AFTER_HEX] [HEADER] 7C 2E 00 00 76 5F 00 00 00 00 00 00 7C 2E 00 00 10 00 00 00 // length [/HEADER] 54 2D 00 00 00 00 00 00 // eShip_None 52 2D 00 00 00 00 00 00 // eShip_Interceptor 55 2D 00 00 00 00 00 00 // eShip_Skyranger 51 2D 00 00 00 00 00 00 // eShip_Firestorm 5A 2D 00 00 00 00 00 00 // eShip_UFOSmallScout 59 2D 00 00 00 00 00 00 // eShip_UFOLargeScout 56 2D 00 00 00 00 00 00 // eShip_UFOAbductor 5B 2D 00 00 00 00 00 00 // eShip_UFOSupply 57 2D 00 00 00 00 00 00 // eShip_UFOBattle 58 2D 00 00 00 00 00 00 // eShip_UFOEthereal 6F 29 00 00 00 00 00 00 // eMPT_SniperSquaddie // +8 // stand-in for FIGHTER 5F 29 00 00 00 00 00 00 // eMPT_HeavySquaddie // +8 // stand-in for RAIDER 73 29 00 00 00 00 00 00 // eMPT_SupportSquaddie // +8 // stand-in for HARVESTER 5B 29 00 00 00 00 00 00 // eMPT_AssaultSquaddie // +8 // stand-in for ASSAULT CARRIER 5A 29 00 00 00 00 00 00 // eMPT_AssaultColonel // +8 // stand-in for TERROR SHIP 53 2D 00 00 00 00 00 00 // eShip_MAX [/AFTER_HEX]
References
Referred to by this article:
- XGTacticalGameCoreData.EProperties
- Help:Managing_files
- Modding_Tools_-_XCOM:EU_2012
- Hex_editing_UPK_files
That refer to this article: