Jump to content

R&D - New Modding Tools


Amineri

Recommended Posts

Amineri, thanks! Yes, I was converting to names as an intermediate step. I didn't realized it can update hex-references directly.

 

I didn't realized, UPKFILE can point to some third upk copy. :smile: I assumed, I have to set correct value for reference update to work, but then I found that I need to specify upk files anyway and then things got messed up completely inside my head. :smile: So yes, it works perfectly. :smile:

Link to comment
Share on other sites

  • Replies 211
  • Created
  • Last Reply

Top Posters In This Topic

Some minor issues:

1. Copy/paste replace is working normally with usual text, but is behaving strange with highlighted code.

2. Highlight is not working until I type in some GUID. It doesn't actually use this GUID, apparently, as it works with incorrect GUID perfectly.

3. When I accidentally pointed reference lookup to compressed upk it did nothing. IMO, should at least show some error message about package being incorrect/compressed.

 

Sorry for not posting it at project home: too much work (IRL) to little time...

Link to comment
Share on other sites

  Quote

 

Sorry for not posting it at project home: too much work (IRL) to little time...

 

No worries, this forum is perfectly fine. :)

 

  Quote

 

1. Copy/paste replace is working normally with usual text, but is behaving strange with highlighted code.

 

I've put in an issue ticket on this, and will look into it. We never actually implemented copy/paste functionality explicitly, we just took advantage of what was built into the Java components used.

 

  Quote

 

2. Highlight is not working until I type in some GUID. It doesn't actually use this GUID, apparently, as it works with incorrect GUID perfectly.

 

Actually I have it set up so that it doesn't attempt to parse any data unless it has a fully filled-out header (version, upkfile, guid, and function). The original purpose of this was to allow it act as a general-purpose text editor that would allow the user to manipulate text without trying to highlight garbage, but subsequent design decisions have made that less relevant.

 

  Quote

 

3. When I accidentally pointed reference lookup to compressed upk it did nothing. IMO, should at least show some error message about package being incorrect/compressed.

 

That's an interesting find -- I hadn't ever had that happen. I'm not sure whether our upk parser knows how to check if the upk it's trying to decode is compressed. We didn't explicitly put such a check in. It may be scanning through the compressed file locations and getting garbage data, which could potentially cause problems.

 

 

Thanks again for the feedback :)

Link to comment
Share on other sites

Another note: I'm experiencing considerable slowdowns when highlighting long code lines.

 

Some of my mods are raw in-place hex editing, so I updated them by copying/pasting an entire code from HxD as one long line. This resulted in considerable slowdown of the entire program.

Link to comment
Share on other sites

I've noticed, that only project which have been opened via "Open Project" are saved in project view (leftmost window). If you create project and then close UPKModder, new project will not be displayed after reopening it. But after you manually open project and close UPKModder, it will be displayed after reopening.
Link to comment
Share on other sites

  • 2 weeks later...

I haven't made much headway in figuring out how to fix the various slowdowns for large files, but I've made headway in handling non-function hex changes in a way that is fairly patch-agnostic yet doesn't require me to build separate parsers for each type of structure in the upk (e.g. enums, variables).

 

Basically I've fixed a small bug that was preventing the direct use of name/object strings within "raw" hex. By raw hex I mean hex code that isn't parse-able as unrealscript. I'm still doing some bug-testing on a new v0.82 that will allow this, but thought I'd post up some examples of how it's working so far.

 

Changing default properties:

MODFILEVERSION=4
UPKFILE=XComStrategyGame.upk 
GUID=UNSPECIFIED // no hex references in file
FUNCTION=Default__XGStrategyAI
KEYWORD=Sets default of m_bFirstMission (used to set hard missions) to false


[BEFORE_HEX]
<|m_bFirstMission|> 00 00 00 00 // name m_bFirstMission
<|BoolProperty|> 00 00 00 00 // name BoolProperty
00 00 00 00 00 00 00 00 // no size for Bool
01 						// bool value true
[/BEFORE_HEX]

[AFTER_HEX]
<|m_bFirstMission|> 00 00 00 00 // name m_bFirstMission
<|BoolProperty|> 00 00 00 00 // name BoolProperty
00 00 00 00 00 00 00 00 // no size for Bool
00 						// bool value false
[/AFTER_HEX]

Extending an enumeration (combined with a RESIZE operation) :

MODFILEVERSION=4
UPKFILE=XComStrategyGame.upk
GUID=UNSPECIFIED // no hex references in file
FUNCTION=EAlienObjective@XGStrategyActorNativeBase
RESIZE=10

[BEFORE_HEX]
[HEADER]
//8F 00 00 00 E6 25 00 00 00 00 00 00 8F 00 00 00 
09 00 00 00 //length
[/HEADER]

<|eObjective_Recon|> 00 00 00 00 // eObjective_Recon
<|eObjective_Scout|> 00 00 00 00 // eObjective_Scout
<|eObjective_Harvest|> 00 00 00 00 // eObjective_Harvest
<|eObjective_Flyby|> 00 00 00 00 // eObjective_Flyby
<|eObjective_Hunt|> 00 00 00 00 // eObjective_Hunt
<|eObjective_Abduct|> 00 00 00 00 // eObjective_Abduct
<|eObjective_Terrorize|> 00 00 00 00 // eObjective_Terrorize
<|eObjective_Infiltrate|> 00 00 00 00 // eObjective_Infiltrate
<|eObjective_MAX|> 00 00 00 00 // eObjective_MAX
[/BEFORE_HEX]


[AFTER_HEX]
[HEADER]
//8F 00 00 00 E6 25 00 00 00 00 00 00 8F 00 00 00 
0b 00 00 00 
[/HEADER]

<|eObjective_Recon|> 00 00 00 00 // eObjective_Recon
<|eObjective_Scout|> 00 00 00 00 // eObjective_Scout
<|eObjective_Harvest|> 00 00 00 00 // eObjective_Harvest
<|eObjective_Flyby|> 00 00 00 00 // eObjective_Flyby
<|eObjective_Hunt|> 00 00 00 00 // eObjective_Hunt
<|eObjective_Abduct|> 00 00 00 00 // eObjective_Abduct
<|eObjective_Terrorize|> 00 00 00 00 // eObjective_Terrorize
<|eObjective_Infiltrate|> 00 00 00 00 // eObjective_Infiltrate
<|eObjective_Infiltrate|> 01 00 00 00 // eObjective_Infiltrate_1
<|eObjective_Infiltrate|> 02 00 00 00 // eObjective_Infiltrate_2
<|eObjective_MAX|> 00 00 00 00 //eObjective_MAX
[/AFTER_HEX]

Changing a variable definition :

MODFILEVERSION=4
UPKFILE=XComStrategyGame.upk
GUID=UNSPECIFIED // no hex references in file
FUNCTION=m_arrObjectiveNames@XGStrategyAI

//increase size of static array and remove enum link

[BEFORE_HEX]
{|CheckpointRecord@XGStrategyAI|} // last?
<|None|> 00 00 00 00              // unknown
{|CheckpointRecord@XGStrategyAI|} // next
08 00 00 00                       // array size
02 80 40 00 00 00 00 00           // flags
<|None|> 00 00 00 00              // unknown
{|EAlienObjective@XGStrategyActorNativeBase|}  // Enum
[/BEFORE_HEX]


[AFTER_HEX]
{|CheckpointRecord@XGStrategyAI|} // last?
<|None|> 00 00 00 00              // unknown
{|CheckpointRecord@XGStrategyAI|} // next
0B 00 00 00                       // array size
02 80 40 00 00 00 00 00           // flags
<|None|> 00 00 00 00              // unknown
00 00 00 00                       // Enum
[/AFTER_HEX]

Note that the GUID is now unspecified since there aren't any hex references remaining in the file.

Link to comment
Share on other sites

I currently use virtual function token to update default properties with UPKModder. :smile: I just enclose 8-byte name indexes into 1B - 16 and run update references. Works perfect. And again, thanks for the great tool! Updated all my mods for patch 3 in a matter of hours. That's fantastic!
Link to comment
Share on other sites

I do a similar thing to convert both function names and import/export objects from references to names.

 

For example, I copied out all of the raw XGItemLibrary default properties raw hex into a UPKmodder document and separated it into bits, then wrapped the name references with 1B ... 16 tokens and prefixed the object references with 00 and ran the reference updater to convert them all to names :

 

 

  Reveal hidden contents

 

 

I had to insert the item values / enums manually, though (with a bit of cut/paste from the enum reverse lookup).

Link to comment
Share on other sites

UPKmodder v0.82 has been uploaded : http://code.google.com/p/upk-modder

 

This has only a single fix :

- Fixed bug preventing processing of embedded names in hex.

This fix allows embedding of namelist entries using <| ... |> delimiters,
and embedding of import/export object entries using {| ... |} delimiters.
The ReferenceUpdater tool built into UPKmodder can convert hex references to valid names using the correct delimiters with the "convert to names" option.
The user can also manually enter names.
Link to comment
Share on other sites

  • 4 weeks later...

I'd typed up some additional information regarding how to change object table entries for johnnylump, so I thought I'd post them here as well for anyone that is interested. Object table data is what is accessed with UE Explorer in the "Table Buffer". The actual object's hex itself is accessed via the "Buffer".

 

  Quote

 

Does this mean changing the first 48 bytes that comes out when you do a buffer dump?

What if I want to change something that appears in the "table buffer" hex instead?
Right, each object has information stored in two different places.
The UE Explorer "Table Buffer" is the a table entry for the object (which has some basic descriptive information about the object, including the file position and file size of the object within the upk). Typically each of the table entries is 68 bytes long. UPKmodder automatically parses the entire object table for each upk it deals with. That's how it builds the reference ID-to-number mapping. The reference number is just the index within this table. XComGame.upk for EW has about 55,000 (= D6D8) entries in the table, so that's why the numbers grow around that big.
UPKmodder extracts the "useful" information from each object using (this is pasted directly out of the Java source code for UPKmodder) :
   private void parseData(int[] data) {
        this.iType = data[0];
        this.iParent = data[1];
        this.iOuter = data[2];
        this.iNamePtr = data[3];
        this.iHighFlags = data[6];
        this.iLowFlags = data[7];
        this.iUpkSize = data[8];
        this.iUpkPos = data[9];
        // TODO: parse/store other values, create getters and other convenience methods (e.g. getNameListIndex(), etc.)
   }

UPKmodder reads the object table data as 4-byte words instead of at the byte-level. Most entries contain 17 4-byte words, although the specifier at position 11 specifies additional 4 byte-words (this value is only non-zero for non-script objects, in my experience).

 

The stuff at the back end of each isn't of use for scripting (e.g. variables, enumerations, functions), and appears to be used to define art asset object info (e.g. 3D meshes, animations, textures, sound files). I'm not sure what the 64 bits of flags all do, either.

 

When you include a RESIZE= command for an object it automatically updates the UpkSize of the current object and the UpkPos of all subsequent objects (all objects that have a position later than the current one).

 

---------------

 

In UE Explorer the regular hex "Buffer" displays the hex for the object itself. The position and size of this object within the upk file is defined via the UpkSize and UpkPos entries within the table entry for the object (i.e. if you looked up the table buffer, read words 8 and 9, opened the upk with HxD and went to that position you'd find the object hex).

 

Functions contain both the run-time "byte-code" bytes as well as 48 bytes of header and 15 bytes of footer information. Typically the only bytes in the header/footer that are changed are the last two words, which contain the memory/file size of the byte-code. The preceding 40 bytes do contain some useful things, including some references (import, object, and/or name). This means that listing the full raw header hex isn't patch-safe, since those values may be updated. However, you can use the name of the object which UPKmodder will attempt to convert to hex at apply/revert-time, which does make it patch-safe. (e.g. {|iType@GetPossibleSpawns@XGDeployAI|})

 

I'm just not sure what the different header/footer bytes do specifically. I'm pretty sure that there are flags defining the function type (e.g. private, static, native, virtual) in the footer. And that some of the header information is used to in creating the linked list of local variables/parameters/return value for the function. Wghost is ahead of me in decoding that stuff.

 

--------------

By default UPKmodder changes the object entry itself (the "buffer dump"). It can change any bytes within the object hex, which is how we make changes to enumerations and variables, for example. For functions this includes the full function header (48 bytes) and footer (15 bytes).

 

UPKmodder will only change hex in the object table when using special commands. For example the RESIZE= command alters size/position of many object table entries.

 

Other commands include:

ACTION=typechange -- a specific command that requires changing both the iType and UpkSize fields of the object table entry. These are specified using OBJECT_TYPE= and SIZE=, for example :

ACTION=typechange

//change object type parameter into int type

[BEFORE_HEX]
OBJECT_TYPE=Core:ObjectProperty@Core
SIZE=2C // hexadecimal
[/BEFORE_HEX]

[AFTER_HEX]
OBJECT_TYPE=Core:IntProperty@Core
SIZE=28 // hexadecimal
[/AFTER_HEX]

For more general-purpose changes to the object table entry for an object, there is the

- ACTION=genericObjectTableChange

 

This allows you to manually change most any entry within the object table entry. Note that this can easily corrupt the upk, in particular if the size/position values are changed incorrectly.

 

For this command new values are specified via keywords, instead of via generic hex changes. This is because UPKmodder has to store copies of most of the table entry data in order to properly decode things, so changing these things is a little trickier and has to be under tighter control. Fortunately there aren't many 'useful' table values, so they can all be specified directly.

 

This command could actually supercede the "typechange" command, as it's more flexible. It allows any or all entries below to be changed --not all values have to be specified.

 

The keywords that allow changes to the object table entry are :

  • OBJECT_TYPE : bytes 0-3 in the table buffer
  • OBJECT_PARENT : bytes 4-7 in the table buffer
  • OBJECT_OUTER : bytes 8-11 in the table buffer
  • OBJECT_NAMEIDX : bytes 12-15 in the table buffer
  • OBJECT_HIGHFLAGS : bytes 20-23 in the table buffer
  • OBJECT_LOWFLAGS : bytes 24-27 in the table buffer
  • OBJECT_SIZE : bytes 28-31 in the table buffer
  • OBJECT_POSITION : bytes 32-35 in the table buffer

 

A few notes about these.

  • OBJECT_TYPE is almost always an import object (reference value is negative), so in UPKmodder would be specified in a patch-safe manner as something like Core:IntProperty@Core, or Core:FunctionProperty@Core.
  • OBJECT_PARENT is only used for classes, and indicates the parent of a class (e.g. XGWeapon is the parent of XGWeapon_AssaultRifle) This is typically a regular object reference
  • OBJECT_OUTER indicates hierarchical ownership. For example in iType@GetPossibleSpawns@XGDeployAI, the iType object has as OUTER the GetPossibleSpawns object, while GetPossibleSpawns has as OUTER the XGDeployAI object. XGDeployAI has a null OUTER
  • OBJECT_NAMEIDX is a reference to a name entry (an index into the name table). This is effectively how each object is named. Objects with the same name reference the same name entry without problem.

UPKmodder expects certain type values in each field :

  • OBJECT_TYPE : must be a valid object (import or export) name (e.g. Core:Function@Core or Core:IntProperty@Core)
  • OBJECT_PARENT : must be a valid object (import of export) name
  • OBJECT_OUTER : must be a valid object (import of export) name
  • OBJECT_NAMEIDX : must be a valid name on the namelist
  • OBJECT_HIGHFLAGS : must be an integer, specified in hexadecimal
  • OBJECT_LOWFLAGS : must be an integer, specifed in hexadecimal
  • OBJECT_SIZE : must be an integer, specifed in hexadecimal
  • OBJECT_POSITION : must be an integer, specifed in hexadecimal

Changing the OBJECT_NAMEIDX for an object is problematic since the name is how the object is referenced in the first place. So changing the name results in UPKmodder being unable to properly locate the object after the change (to verify correct install and to be able to revert the change).

 

IMPORTANT!!!

When changing the NAMEIDX, leave the name of the current object out of the FUNCTION= (alternative usage OBJECT_ENTRY=) specifier.

 

For example :

MODFILEVERSION=4
UPKFILE=XComGame.upk
GUID=1C 18 A1 1A 2B C3 34 4E 8B 2C 72 33 CD 16 7E 3E // XComGame_EW_patch3.upk
OBJECT_ENTRY=CheckpointRecord_XGWeapon@XGWeapon
ACTION=genericObjectTableChange

//iOverheatChance@CheckpointRecord_XGWeapon@XGWeapon
//alter CheckpointRecord_XGWeapon so that m_iTurnFired is the member, not iOverheatChance

[BEFORE_HEX]
OBJECT_NAMEIDX=iOverheatChance
[/BEFORE_HEX]


[AFTER_HEX]
OBJECT_NAMEIDX=m_iTurnFired
[/AFTER_HEX]

In this case the object iOverheatChance@CheckpointRecord_XGWeapon@XGWeapon is being transformed into the object m_iTurnFired@CheckpointRecord_XGWeapon@XGWeapon.

 

The CheckpointRecord_XGWeapon@XGWeapon object itself is not being changed. Instead if UPKmodder sees that the OBJECT_NAMEIDX= field is being changed, it automatically prepends the value to what is specified in the OBJECT_ENTRY field.

 

So when applying the change, it locates the object with the name "iOverheatChance@CheckpointRecord_XGWeapon@XGWeapon"

When reverting the change is locates the object with the name "m_iTurnFired@CheckpointRecord_XGWeapon@XGWeapon"

Then testing for apply/revert status, it tests both.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...