Enum Expansion - XCOM:EU 2012

From Nexus Mods Wiki
Jump to: navigation, search


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:



That refer to this article: