Jump to content

Expanding function size in UPK


wghost81

Recommended Posts

I think you may be right that's its just a touch of perfectionism (which when it comes to modding unreal bytecode is not necessarily a bad thing!).

 

The really ironic part is ... you should ask XMTS about my extremely sloppy coding in Java, which has (I suspect) come close to breaking him a few times ;)

 

 

The offsetting file positions would basically be exactly what EW patch 1 and EU patch 6 did... Simply put not very popular :smile:

 

I'm not quite sure I understand what you mean here ...

 

Basically any patch is going to do 2 things:

1) Change the object/name/import tables, rendering all references in bytecode no longer valid

2) Change the size of the upk, rendering absolute file offset no longer valid

 

To combat these issues :

  • I've built the modding tool that can perform reference updating by using full names as in-betweens ...
    • (e.g. E3 11 00 00 => MaxActiveAIUnits@XGTacticalGameCoreNativeBase for XComGame.upk for EW patch 1, then the name would be resolved to the new reference in the new version)
  • To combat absolute file offset issues I always use BEFORE / AFTER search and replace instead of a file position
    • In order to make search and replace speedy I use the object information to narrow the scope of the search considerably -- typically from ~12 MB to ~ 500 bytes

So there's really not a great need any longer to be using absolute file offsets, except for legacy stuff. Basically using absolute file offsets and fixed hex is the most brittle solution, the most guaranteed to break with a patch.

 

 

In addition to this there is the perfectionist side of things. Perfectionist is usually a very good trait for any programmer but in this case I think a tad of "just getting it done with least amount of effort" approach may be more rational :smile:

 

Except now I've already spent the time (about 8 or 10 hours to write the function up and test it) ;)

 

Now that I have the function written it only takes me a single click (and the computer about 0.5 seconds) to put an in-place function in. This is pretty trivial in comparison to the amount of time I actually have to spend working on the designing the hex itself :D

 

Once the hex is written and tested, for a "final" install I certainly wouldn't see any sort of problem with appending -- not the least is that doing a whole sequence of expand operations (hopefully not necessary!) would go faster by copying/appending.

 

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

 

My motivation for this is actually creating my ultimate version of the dynamic alien upgrade mod, which will allow for things like dynamically upgrading units shield HP (aka Mechtoid shield).

 

I'm working on that now so once I'm done I'll post up my code and we can all discuss the various options for installing it ^_^.

 

I'd very much love it if wghost built tools that could easily install such mods on a user's system. As nicely as the modding tool is coming along, it definitely is aimed at making a modder's life easier, not at making installation easy!

Link to comment
Share on other sites

  • Replies 61
  • Created
  • Last Reply

Top Posters In This Topic

 

 

The offsetting file positions would basically be exactly what EW patch 1 and EU patch 6 did... Simply put not very popular :smile:

 

I'm not quite sure I understand what you mean here ...

 

Basically any patch is going to do 2 things:

1) Change the object/name/import tables, rendering all references in bytecode no longer valid

2) Change the size of the upk, rendering absolute file offset no longer valid

 

To combat these issues :

  • I've built the modding tool that can perform reference updating by using full names as in-betweens ...
    • (e.g. E3 11 00 00 => MaxActiveAIUnits@XGTacticalGameCoreNativeBase for XComGame.upk for EW patch 1, then the name would be resolved to the new reference in the new version)
  • To combat absolute file offset issues I always use BEFORE / AFTER search and replace instead of a file position
    • In order to make search and replace speedy I use the object information to narrow the scope of the search considerably -- typically from ~12 MB to ~ 500 bytes

So there's really not a great need any longer to be using absolute file offsets, except for legacy stuff. Basically using absolute file offsets and fixed hex is the most brittle solution, the most guaranteed to break with a patch.

But the point really is the legacy stuff.

To all existing mods the effect would be the same as if an official patch had been released.

 

If we jump forward a month or two, when using new "high level" tools for mod distribution is most likely standard, then I agree that it is a non-issue. It is in the meantime when "legacy" is normal that the problem exist.

Link to comment
Share on other sites

Okay, here's that concrete example I was saying I'd post up.

 

The original code in XComGame.upk >> XGTacticalGameCore :

 

 

simulated function ModifyStatsByDifficulty(out TCharacter kCharacter)
{
    local TCharacterBalance Mods;

    GetCharacterBalanceMods(byte(kCharacter.iType), Mods);
    // End:0x219
    if(Mods.eType == kCharacter.iType)
    {
        kCharacter.aStats[1] += Mods.iAim;
        kCharacter.aStats[2] += Mods.iDefense;
        kCharacter.aStats[3] += Mods.iMobility;
        kCharacter.aStats[7] += Mods.iWill;
        kCharacter.aStats[13] += Mods.iCritHit;
        kCharacter.aStats[0] += Mods.iHP;
        kCharacter.aStats[12] += Mods.iDamage;
    }
    //return;    
} 

 

 

 

It does the basic job of adding stats based on difficulty, using the helper function GetCharacterBalanceMods (which isn't very big either).

 

Here is the new function, as expanded/replaced and decompiled by UE Explorer :

 

 

simulated function ModifyStatsByDifficulty(out TCharacter kCharacter)
{
    local TCharacterBalance Mods;

    // End:0x1ED
    foreach BalanceMods_Easy(Mods,)
    {
        // End:0x1EC
        if(Mods.iCritHit == m_iDifficulty)
        {
            // End:0x2EC
            if(Mods.eType == kCharacter.iType)
            {
                kCharacter.aStats[1] += Mods.iAim;
                kCharacter.aStats[2] += Mods.iDefense;
                kCharacter.aStats[3] += Mods.iMobility;
                kCharacter.aStats[7] += Mods.iWill;
                kCharacter.aStats[0] += Mods.iHP;
                kCharacter.aStats[12] += Mods.iDamage;
            }            
        }        
        iWeapon = 16777215;
        // End:0x318
        if(XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle != none)
        {
            iWeapon = XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle.STAT_GetStat(1);
            // End:0x2EC
            if(IsOptionEnabled(7))
            {
                iWeapon *= default.SW_MARATHON;
            }
        }
        iWeapon = (iWeapon * default.MaxActiveAIUnits[m_iDifficulty]) / 100;
    }
    // End:0x61F
    foreach BalanceMods_Normal(Mods,)
    {
        // End:0x61E
        if(Mods.iCritHit > m_iDifficulty)
        {
            // End:0x61E
            if(Mods.eType == kCharacter.iType)
            {
                kCharacter.aStats[12] += Mods.iDamage;
                kCharacter.aStats[3] += Mods.iMobility;
                kCharacter.aStats[1] += (Mods.iAim % 100);
                kCharacter.aStats[13] += (Mods.iAim / 100);
                kCharacter.aStats[2] += (Mods.iDefense % 100);
                kCharacter.aStats[4] += (Mods.iDefense / 100);
                kCharacter.aStats[4] += (100 * (Mods.iWill / 100));
                kCharacter.aStats[7] += (Mods.iWill % 100);
                kCharacter.aStats[0] += (Mods.iHP % 100);
                kCharacter.aStats[5] += (Mods.iHP / 100);
            }
        }        
    }    
    //return;    
} 

 

 

 

I've removed the call to the helper function, and there's a lot more functionality.

 

 

It uses the BalanceMods_Easy array to set base stats based on difficulty (which is embedded in the iCritHit field now). It then uses the BalanceMods_Normal config array to configure the dynamic upgrades. I decided against trying to force someone to configure each of the dynamic upgrade trajectories separately for each difficulty, so instead there is a time modifier using MaxActiveAIUnits to adjust for difficulty. For dynamic upgrades it is possible to adjust 10 stats. The normal 7, Shield HP, and two new stats that I introduced in Long War, Damage reduction and regeneration.

 

 

All of this requires that the new function be 0x293 = 659 file bytes larger. And no, I didn't try and break your earlier 640 bytes number ... it just happened.

 

So here is the modfile that I created that the modding tool uses to resize and replace the function :

 

 

MODFILEVERSION=4
UPKFILE=XComGame.upk 
GUID=5B 06 B8 18 67 22 12 44 85 9B A8 5B 9D 57 1D 4B  // XComGame_EW_patch1.upk
FUNCTION=ModifyStatsByDifficulty@XGTacticalGameCore
RESIZE=293 // size increase in hex -- function will be significantly resized up

//complete rewrite of ModifyStats by difficulty to provide both initial upgrades and dynamic upgrades

// initial upgrades vary by difficulty (coded into iCritHit) -- all other variables are as described
//    initial upgrades are drawn from BalanceMods_Easy

// dynamic upgrades vary only based on dayCount. Any difficulty-related changes are not stored in the CharacterBalance struct
//    may use MaxActiveAIUnits to store difficulty variation for dynamic upgrade appearance and hard-code this to max.
//    dynamic upgrades are drawn from BalanceMods_Normal
//    dynamic upgrade encoding:
//       	iDamage	(damage)
// 	iCritHit	(day count)
// 	Aim	(low 2 digits Aim, high 2 digits CritHit)
// 	iDefense	(low 2 digits Defense, high 2 digits damage reduction )
// 	iHP	(low 2 digits HP, high 2 digits ShieldHP)
// 	iMobility	(Mobility)
//	iWill	(low 2 digits Will, high 2 digits regeneration )

[BEFORE_HEX]
[HEADER]
1C 02 00 00 4C 01 00 00 
[/HEADER]
[code]
//GetCharacterBalanceMods(byte(kCharacter.iType), Mods)
1B 90 37 00 00 00 00 00 00 38 3D 35 56 0F 00 00 58 0F 00 00 00 00 48 AE 84 00 00 00 AD 84 00 00 16 

//if(Mods.eType == kCharacter.iType)
07 19 02 9A 38 3A 35 D4 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 35 56 0F 00 00 58 0F 00 00 00 00 48 AE 84 00 00 16 

	//kCharacter.aStats[1] += Mods.iAim
	A1 1A 26 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D1 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[2] += Mods.iDefense
	A1 1A 2C 02 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D0 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[3] += Mods.iMobility
	A1 1A 2C 03 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CE 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[7] += Mods.iWill
	A1 1A 2C 07 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CD 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[13] += Mods.iCritHit
	A1 1A 2C 0D 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D2 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[0] += Mods.iHP
	A1 1A 25 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CF 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//kCharacter.aStats[12] += Mods.iDamage
	A1 1A 2C 0C 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D3 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

//return
04 0B 

//EOS
53 
[/CODE]
[/BEFORE_HEX]

// uses local variable iWeapon@CalcHitChange_NonTargetUnit@XGTacticalGameCore
[AFTER_HEX]
[HEADER]
23 06 00 00 DF 03 00 00 
[/HEADER]
[code]
// foreach BalanceMods_Easy(Mods,) // apply base stat deltas based on difficulty
58 01 0E 12 00 00 00 AD 84 00 00 00 4A ED 01 

	//if(Mods.iCritHit == m_iDifficulty) // check for difficulty match
	07 EC 01 9A 35 D2 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 01 87 83 00 00 16 
	
		//if(Mods.eType == kCharacter.iType) // check for character type match
		07 EC 02 9A 38 3A 35 D4 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 35 56 0F 00 00 58 0F 00 00 00 00 48 AE 84 00 00 16 

			//kCharacter.aStats[1] += Mods.iAim
			A1 1A 26 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D1 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[2] += Mods.iDefense
			A1 1A 2C 02 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D0 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[3] += Mods.iMobility
			A1 1A 2C 03 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CE 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[7] += Mods.iWill
			A1 1A 2C 07 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CD 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[0] += Mods.iHP
			A1 1A 25 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CF 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[12] += Mods.iDamage
			A1 1A 2C 0C 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D3 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

	//Iterator Next
	31 

	//Iterator Pop
	30 

// iWeapon = 16777215;
0F 00 FD 83 00 00 1D FF FF FF 00 

//if(XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle != none) 
07 18 03 77 19 2E BD 32 00 00 19 12 20 36 FE FF FF 0A 00 9E F9 FF FF 00 1C DE FB FF FF 16 09 00 5C F9 FF FF 00 01 5C F9 FF FF 09 00 C9 32 00 00 00 01 C9 32 00 00 2A 16 

	//iWeapon = XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle.STAT_GetStat(1) 
	0F 00 FD 83 00 00 19 19 2E BD 32 00 00 19 12 20 36 FE FF FF 0A 00 9E F9 FF FF 00 1C DE FB FF FF 16 09 00 5C F9 FF FF 00 01 5C F9 FF FF 09 00 C9 32 00 00 00 01 C9 32 00 00 0C 00 06 B0 00 00 00 1B 0F 73 00 00 00 00 00 00 2C 01 16 

	//if(IsOptionEnabled(7)) 
	07 EC 02 1B 3B 45 00 00 00 00 00 00 2C 07 16 

		//iWeapon *= SW_MARATHON; 
		9F 00 FD 83 00 00 02 1B 11 00 00 16 

	// iWeapon = (iWeapon * MaxActiveAIUnits[m_iDifficulty]) / 100; // scale effective time based on difficulty
	0F 00 FD 83 00 00 91 90 00 FD 83 00 00 10 01 87 83 00 00 02 E3 11 00 00 16 2C 64 16 

// foreach BalanceMods_Normal(Mods,) // apply dynamic stat deltas based on STAT_GetStat(1) (eRecap_Days)
58 01 0D 12 00 00 00 AD 84 00 00 00 4A 1F 06 

	//if(Mods.iCritHit > iWeapon) // check if enough days have elapsed
	07 1E 06 97 35 D2 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 01 87 83 00 00 16 
	
		//if(Mods.eType == kCharacter.iType) // check for character type match
		07 1E 06 9A 38 3A 35 D4 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 35 56 0F 00 00 58 0F 00 00 00 00 48 AE 84 00 00 16 

			//kCharacter.aStats[12] += Mods.iDamage // straight assignment
			A1 1A 2C 0C 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 D3 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[3] += Mods.iMobility // straight assignment
			A1 1A 2C 03 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 35 CE 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 16 

			//kCharacter.aStats[1] += Mods.iAim % 100   // only low 2 digits
			A1 1A 2C 01 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 FD 35 D1 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[13] += Mods.iAim / 100 // only upper 2 digits // Crit Assignment
			A1 1A 2C 0D 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 91 35 D1 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[2] += Mods.iDefense % 100   // only low 2 digits
			A1 1A 2C 02 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 FD 35 D0 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[4] += Mods.iDefense / 100   // only high 2 digits // store regeneration in low 2 digits of Strength stat
			A1 1A 2C 04 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 91 35 D0 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[4] += 100*(Mods.iWill / 100)   // only high 2 digits // store damage reduction in high 2 digits of Strength stat
			A1 1A 2C 04 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 90 2C 64 91 35 CD 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 16 

			//kCharacter.aStats[7] += Mods.iWill % 100   // only low 2 digits
			A1 1A 2C 07 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 FD 35 CD 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[0] += Mods.iHP % 100 // only low 2 digits
			A1 1A 2C 00 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 FD 35 CF 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

			//kCharacter.aStats[5] += Mods.iHP / 100 // only upper 2 digits  // Shield HP assignment
			A1 1A 2C 05 35 51 0F 00 00 58 0F 00 00 00 01 48 AE 84 00 00 91 35 CF 0F 00 00 D5 0F 00 00 00 00 00 AD 84 00 00 2C 64 16 16 

	//Iterator Next
	31 

	//Iterator Pop
	30 

//return
04 0B 

//EOS
53 
[/CODE]
[/AFTER_HEX] 

 

 

 

Ideally an installer could take this modfile and simply apply it to the target upk. It should contain all of the necessary information to make it happen.

 

Now, this display of the modfile is not independent of patch version changes, as it is still using numeric references. However it is generally cleaner to read this way. The modfile is also tagged at the top with the GUID of the upk for which the numeric references are valid.

 

 

Here is the same modfile with all references auto-converted to full names :

 

 

MODFILEVERSION=4
UPKFILE=XComGame.upk 
GUID=UNSPECIFIED // no hex references in file
FUNCTION=ModifyStatsByDifficulty@XGTacticalGameCore
RESIZE=293 // size increase in hex -- function will be significantly resized up

//complete rewrite of ModifyStats by difficulty to provide both initial upgrades and dynamic upgrades

// initial upgrades vary by difficulty (coded into iCritHit) -- all other variables are as described
//    initial upgrades are drawn from BalanceMods_Easy

// dynamic upgrades vary only based on dayCount. Any difficulty-related changes are not stored in the CharacterBalance struct
//    may use MaxActiveAIUnits to store difficulty variation for dynamic upgrade appearance and hard-code this to max.
//    dynamic upgrades are drawn from BalanceMods_Normal
//    dynamic upgrade encoding:
//       	iDamage	(damage)
// 	iCritHit	(day count)
// 	Aim	(low 2 digits Aim, high 2 digits CritHit)
// 	iDefense	(low 2 digits Defense, high 2 digits damage reduction )
// 	iHP	(low 2 digits HP, high 2 digits ShieldHP)
// 	iMobility	(Mobility)
//	iWill	(low 2 digits Will, high 2 digits regeneration )

[BEFORE_HEX]
[HEADER]
1C 02 00 00 4C 01 00 00 
[/HEADER]
[code]
//GetCharacterBalanceMods(byte(kCharacter.iType), Mods)
1B <|GetCharacterBalanceMods|> 00 00 00 00 38 3D 35 {|iType@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 00 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

//if(Mods.eType == kCharacter.iType)
07 19 02 9A 38 3A 35 {|eType@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iType@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 00 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[1] += Mods.iAim
	A1 1A 26 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iAim@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[2] += Mods.iDefense
	A1 1A 2C 02 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iDefense@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[3] += Mods.iMobility
	A1 1A 2C 03 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iMobility@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[7] += Mods.iWill
	A1 1A 2C 07 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iWill@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[13] += Mods.iCritHit
	A1 1A 2C 0D 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iCritHit@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[0] += Mods.iHP
	A1 1A 25 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iHP@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//kCharacter.aStats[12] += Mods.iDamage
	A1 1A 2C 0C 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iDamage@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

//return
04 0B 

//EOS
53 
[/CODE]
[/BEFORE_HEX]

// uses local variable iWeapon@CalcHitChange_NonTargetUnit@XGTacticalGameCore
[AFTER_HEX]
[HEADER]
23 06 00 00 DF 03 00 00 
[/HEADER]
[code]
// foreach BalanceMods_Easy(Mods,) // apply base stat deltas based on difficulty
58 01 {|BalanceMods_Easy@XGTacticalGameCoreNativeBase|} 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 00 4A ED 01 

	//if(Mods.iCritHit == m_iDifficulty) // check for difficulty match
	07 EC 01 9A 35 {|iCritHit@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 01 {|m_iDifficulty@XGTacticalGameCore|} 16 
	
		//if(Mods.eType == kCharacter.iType) // check for character type match
		07 EC 02 9A 38 3A 35 {|eType@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iType@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 00 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[1] += Mods.iAim
			A1 1A 26 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iAim@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[2] += Mods.iDefense
			A1 1A 2C 02 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iDefense@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[3] += Mods.iMobility
			A1 1A 2C 03 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iMobility@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[7] += Mods.iWill
			A1 1A 2C 07 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iWill@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[0] += Mods.iHP
			A1 1A 25 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iHP@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[12] += Mods.iDamage
			A1 1A 2C 0C 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iDamage@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

	//Iterator Next
	31 

	//Iterator Pop
	30 

// iWeapon = 16777215;
0F 00 {|iWeapon@CalcHitChance_NonUnitTarget@XGTacticalGameCore|} 1D FF FF FF 00 

//if(XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle != none) 
07 18 03 77 19 2E {|XComTacticalGRI|} 19 12 20 {|Core:Engine@Engine|} 0A 00 {|Core:ReturnValue@GetCurrentWorldInfo@Engine@Engine|} 00 1C {|Core:GetCurrentWorldInfo@Engine@Engine|} 16 09 00 {|Core:GRI@WorldInfo@Engine|} 00 01 {|Core:GRI@WorldInfo@Engine|} 09 00 {|m_kBattle@XComTacticalGRI|} 00 01 {|m_kBattle@XComTacticalGRI|} 2A 16 

	//iWeapon = XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle.STAT_GetStat(1) 
	0F 00 {|iWeapon@CalcHitChance_NonUnitTarget@XGTacticalGameCore|} 19 19 2E {|XComTacticalGRI|} 19 12 20 {|Core:Engine@Engine|} 0A 00 {|Core:ReturnValue@GetCurrentWorldInfo@Engine@Engine|} 00 1C {|Core:GetCurrentWorldInfo@Engine@Engine|} 16 09 00 {|Core:GRI@WorldInfo@Engine|} 00 01 {|Core:GRI@WorldInfo@Engine|} 09 00 {|m_kBattle@XComTacticalGRI|} 00 01 {|m_kBattle@XComTacticalGRI|} 0C 00 {|ReturnValue@STAT_GetStat@XGBattle|} 00 1B <|STAT_GetStat|> 00 00 00 00 2C 01 16 

	//if(IsOptionEnabled(7)) 
	07 EC 02 1B <|IsOptionEnabled|> 00 00 00 00 2C 07 16 

		//iWeapon *= SW_MARATHON; 
		9F 00 {|iWeapon@CalcHitChance_NonUnitTarget@XGTacticalGameCore|} 02 {|SW_MARATHON@XGTacticalGameCoreNativeBase|} 16 

	// iWeapon = (iWeapon * MaxActiveAIUnits[m_iDifficulty]) / 100; // scale effective time based on difficulty
	0F 00 {|iWeapon@CalcHitChance_NonUnitTarget@XGTacticalGameCore|} 91 90 00 {|iWeapon@CalcHitChance_NonUnitTarget@XGTacticalGameCore|} 10 01 {|m_iDifficulty@XGTacticalGameCore|} 02 {|MaxActiveAIUnits@XGTacticalGameCoreNativeBase|} 16 2C 64 16 

// foreach BalanceMods_Normal(Mods,) // apply dynamic stat deltas based on STAT_GetStat(1) (eRecap_Days)
58 01 {|BalanceMods_Normal@XGTacticalGameCoreNativeBase|} 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 00 4A 1F 06 

	//if(Mods.iCritHit > iWeapon) // check if enough days have elapsed
	07 1E 06 97 35 {|iCritHit@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 01 {|m_iDifficulty@XGTacticalGameCore|} 16 
	
		//if(Mods.eType == kCharacter.iType) // check for character type match
		07 1E 06 9A 38 3A 35 {|eType@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iType@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 00 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[12] += Mods.iDamage // straight assignment
			A1 1A 2C 0C 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iDamage@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[3] += Mods.iMobility // straight assignment
			A1 1A 2C 03 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 35 {|iMobility@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 16 

			//kCharacter.aStats[1] += Mods.iAim % 100   // only low 2 digits
			A1 1A 2C 01 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} FD 35 {|iAim@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[13] += Mods.iAim / 100 // only upper 2 digits // Crit Assignment
			A1 1A 2C 0D 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 91 35 {|iAim@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[2] += Mods.iDefense % 100   // only low 2 digits
			A1 1A 2C 02 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} FD 35 {|iDefense@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[4] += Mods.iDefense / 100   // only high 2 digits // store regeneration in low 2 digits of Strength stat
			A1 1A 2C 04 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 91 35 {|iDefense@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[4] += 100*(Mods.iWill / 100)   // only high 2 digits // store damage reduction in high 2 digits of Strength stat
			A1 1A 2C 04 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 90 2C 64 91 35 {|iWill@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 16 

			//kCharacter.aStats[7] += Mods.iWill % 100   // only low 2 digits
			A1 1A 2C 07 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} FD 35 {|iWill@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[0] += Mods.iHP % 100 // only low 2 digits
			A1 1A 2C 00 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} FD 35 {|iHP@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

			//kCharacter.aStats[5] += Mods.iHP / 100 // only upper 2 digits  // Shield HP assignment
			A1 1A 2C 05 35 {|aStats@TCharacter@XGTacticalGameCoreNativeBase|} {|TCharacter@XGTacticalGameCoreNativeBase|} 00 01 48 {|kCharacter@ModifyStatsByDifficulty@XGTacticalGameCore|} 91 35 {|iHP@TCharacterBalance@XGTacticalGameCoreNativeBase|} {|TCharacterBalance@XGTacticalGameCoreNativeBase|} 00 00 00 {|Mods@ModifyStatsByDifficulty@XGTacticalGameCore|} 2C 64 16 16 

	//Iterator Next
	31 

	//Iterator Pop
	30 

//return
04 0B 

//EOS
53 
[/CODE]
[/AFTER_HEX] 

 

 

 

Note that the GUID was converted (automatically) to unknown. Since there are no longer any numeric references it applies equally well to any upk version -- depending on the code itself, of course.

 

This version is a bit messy to read (as the names aren't as compact as the numeric references), but it's as version independent as I can make it, while still getting the functionality desired.

 

Barring Firaxis actually changing the code in the function (or removing variables/functions I 'borrowed'), with a patcher that can handle it this will persist through multiple patches.

 

So this is my preferred distribution format, and is the one that the UPKmodder natively uses. However if any tweaks are required it's possible to create an export function to massage it into a different format.

Link to comment
Share on other sites

Amineri, it seems, I finally understood your dilemma. Trying to figure out such a complex subject using foreign language is a bit difficult for me. :smile:

 

Your proposed solution to combat offset shifts is before/after style patching. No matter where the code is, patcher will find it and will replace it, expanding corresponding function if needed.

 

But as it seems to me, it is still low-level solution of a high-level problem.

 

First of all, before/after style is incompatible with move/expand function idea, as it may find and patch "garbage" function instead of real one, which was moved. Unless, of course, you determine real function offset first. But, if you do so you really don't need to search anything: you already at function position and you know its structure and where to apply all the changes.

 

What I'm trying to say, is that all objects are "smart", because UE uses object-oriented approach. It means, an object knows its type, its location and whatever else is needed to handle it correctly. And since object is "smart", we can simply "ask" it to change. In this terms, data chunks in before-hex are "dumb": they're just bytes, they don't know anything about themselves, so we can't ask them anything.

 

In object-oriented approach you write a class and encapsulate actual low-level tasks in high-level smart functions. And since you've done this, you can use this class to work with packages in the safe way and don't think about low-level problems, because since then you'll be working with high-level objects only. It means, when you're working on a mod with 100+ functions to change, you don't need to think about how they are placed inside packages. You think about actual functions content only. While in progress, it may result in very internally-messy upk, but the beauty of it is, first, your mod will still work perfectly, second, when you apply your "smart" changes to fresh vanilla upk, the mod will work the same way and resulting package file will be a lot cleaner.

 

Regarding backups and base upks for my work. I always use clean vanilla packages for a new mod. It is a safer way, IMO, to test/debug a new changes.

 

And regarding "re-cooking" packages. From what I've read about objects order, it seems that child classes required to follow after parent classes to perform "seek-free" package loading. Some info can be found here: https://udn.epicgames.com/Three/ContentCooking.html.

Link to comment
Share on other sites

Object-oriented patching and using relative offsets is better than offset-oriented patching. I remember bokauk wanted to redo Custom Mods feature, but I don't know how.

 

When it comes to old mods, it is always good, if they are updated for the latest version of the game. And in the process is possible to adapt the mod for updated tool.

 

Amineri, I think you could switch to UPK patching with your new tool instead of distributing modded upk. Do smaller mods compatible with LW have to be installed after LW? Also if you develop a mod and release few bug fixes or updates reexpanding a function by appending, a patch will take care of garbage collection. But in-place approach opens new possibilities.

 

Hm, new objects :smile: New functions, names, config variables and expanded enums. That'd be fantastic! Even if doing crazy things like altering 55000+- objects.

Link to comment
Share on other sites

Hm, new objects :smile: New functions, names, config variables and expanded enums. That'd be fantastic! Even if doing crazy things like altering 55000+- objects.

I've given up on maps, but I haven't given up on objects data format. :smile: There is an interesting flag in class object - 0x00000004: Config. Config file name is also present. So, theoretically, it might be possible to add some new config entries for class variables.

 

I think we might be able to add some new objects by appending them to the end of object list table. If we could figure out the cooking order. But expanding existing objects, like enums, might not be possible.

Edited by wghost81
Link to comment
Share on other sites

But as it seems to me, it is still low-level solution of a high-level problem.

 

First of all, before/after style is incompatible with move/expand function idea, as it may find and patch "garbage" function instead of real one, which was moved. Unless, of course, you determine real function offset first. But, if you do so you really don't need to search anything: you already at function position and you know its structure and where to apply all the changes.

 

What I'm trying to say, is that all objects are "smart", because UE uses object-oriented approach. It means, an object knows its type, its location and whatever else is needed to handle it correctly. And since object is "smart", we can simply "ask" it to change. In this terms, data chunks in before-hex are "dumb": they're just bytes, they don't know anything about themselves, so we can't ask them anything.

 

In object-oriented approach you write a class and encapsulate actual low-level tasks in high-level smart functions. And since you've done this, you can use this class to work with packages in the safe way and don't think about low-level problems, because since then you'll be working with high-level objects only. It means, when you're working on a mod with 100+ functions to change, you don't need to think about how they are placed inside packages. You think about actual functions content only. While in progress, it may result in very internally-messy upk, but the beauty of it is, first, your mod will still work perfectly, second, when you apply your "smart" changes to fresh vanilla upk, the mod will work the same way and resulting package file will be a lot cleaner.

 

 

I think I sort of indirectly backed into the object-oriented approach you describe here.

 

UPKmodder doesn't perform a global search over the entire UPK looking for the BEFORE hex (as you point out you could potentially find garbage data, but for anything over ~10 bytes that starts to get really unlikely).

 

Instead UPKmodder uses the FUNCTION=<name> to perform a search (based on name with all owners) and find the correct object. Technically a non-function object name can be used in place of function, I suppose. It then performs only a very limited search within the confines defined by the object entry, which provides the specific byte offset. This is primarily intended to support changing smaller (and possibly multiple) sections within a single function.

 

For the resize and replace operation it still starts by looking up the object based on name, and then doing a search within that area to find the specific byte offset of the BEFORE hex. Because S&R with resize is fairly specific this is somewhat redundant but provides as a useful error checking mechanism. Doing this limited search also provides the insertion point.

 

The basic insert operation copies into a new file in three "chunks"

1) Front of original file from beginning of file to beginning of the found BEFORE hex)

2) Insert AFTER hex into new file

3) Back of original file from end of BEFORE hex to end of file

 

The next step is patching up the object table entries. The object changed has its size increased (but position is unchanged).

 

Then all object entries are scanned and any with a position after the insertion point have their position increased by the number of bytes added.

 

To undo the operation the roles of the BEFORE and AFTER hex are swapped (in the code I map these to FIND and REPLACE temporary variables). Instead of adding bytes this removes bytes, as the REPLACE is smaller than the FIND.

 

 

When it comes to old mods, it is always good, if they are updated for the latest version of the game. And in the process is possible to adapt the mod for updated tool.

 

This is basically what I'm doing with all of my mods, both the released ones and the ones I tweaked up just for Long War.

After they get put into the new format it will be a lot easier to update/maintain them for new patch versions, as all the necessary meta-data is tagged in with the hex changes.

 

 

Amineri, I think you could switch to UPK patching with your new tool instead of distributing modded upk. Do smaller mods compatible with LW have to be installed after LW? Also if you develop a mod and release few bug fixes or updates reexpanding a function by appending, a patch will take care of garbage collection. But in-place approach opens new possibilities.

 

It will be possible to load up .upk_mod files and apply them to the game. XMTS and I are working on a "modproject" view that enables managing large numbers of modfiles. Ultimately when UPKmodder loads a modproject it should (in the background) search the target upk and indicate the install status of the modfiles. I'm also looking at a "project apply/revert" functionality.

 

However I'm keeping the focus on building it as a modding tool, not as a mod installer. Have to have some sort of focus and the mod-creation tools have a bit of a gap. Of course all this work is being released under GPL3 so people can take it and adapt it into a mod installer.

 

As to install order with Long War ... you'd have to check with JL, as he handles the installer side of things. I believe his current plan is to replace with already-modded upks instead of trying to apply all of the mods directly, which would require Long War to be first in the install order.

Link to comment
Share on other sites

Thanks for clarification Amineri. I asked, because distributing a patch requires less bandwitch than distributing entire upk.

 

It's wonderful, we have proper tools at disposal now. XCOMModHelper, UPKPatcher and PatcherGUI, developed UPKModder, XCOM ToolBoks. Maybe it is offtopic, but distribution of mods is a nightmare without reliable tools. We miss one that can patch exe+upk+int files with latest discoveries functionality. Also there must be some compatibility. For an example is wrong, when two mods are distributing entire XComGame.int overwriting each other's changes. Not mentioning modding the same value in DGC or the same function, because that's too much offtopic and a job for mutators, even they were not planned for large scale mods.

 

I respect intentions of every tool's author and am very grateful for any change, that can improve current situation. I'd be the most happy, if UPKModder handles mod's creation/updates and PatcherGUI distribution. Also my wish is, that we produce compatible mods so people get the best of our efforts. But I can only watch you guys develop, help with suggestions of mod's author and testing.

 

 

I've given up on maps, but I haven't given up on objects data format. :smile: There is an interesting flag in class object - 0x00000004: Config. Config file name is also present. So, theoretically, it might be possible to add some new config entries for class variables.

I think we might be able to add some new objects by appending them to the end of object list table. If we could figure out the cooking order. But expanding existing objects, like enums, might not be possible.

 

Been following Map scripts thread and I agree you deserve a break from it :)

 

Being able to add config variables is helpfull, if people want to mod someone's mod (like LW), because everyone knows text editor. I thought, if it comes to recomputing 55k indexes, that throwing in new object could be possible. Like new name and function variable.

Edited by Drakous79
Link to comment
Share on other sites

I think the primary issue for Long War installation has been reliability and ability to run some sort of bulk installer.

 

The installer has to handle any upk changes, including decompressing the upks, removing the .uncompressed_size files, patching the hex into the upks, making changes to the config files, and making changes to the localization files.

 

The only part of this that I've coded up is patching the upk files.

 

One area that I'm very definitely not working with UPKmodder is in automated updating of config and localization files, which are critically important for installing an actual mod.

 

-----------

 

Back discussing resizing functions (and objects in general)...

 

Even though this is written in Java and not native machine-code, the in-place resize + apply/revert functionality is still reasonably snappy.

 

I instrumented the time and spammed apply/revert several times (also to verify that the upk was not corrupted), and here were the results or apply -> revert -> apply -> revert . The final result is a upk file that is byte-wise identical to the original upk.

[16:09:57] [INFO] Resize: inserted new hex, took 180ms
[16:09:57] [INFO] Resize: rewrote object table, took 61ms
[16:09:57] [INFO] Function resized and AFTER Hex Installed
[16:09:59] [INFO] Resize: inserted new hex, took 154ms
[16:09:59] [INFO] Resize: rewrote object table, took 61ms
[16:09:59] [INFO] Function resized and BEFORE Hex Installed
[16:10:02] [INFO] Resize: inserted new hex, took 86ms
[16:10:02] [INFO] Resize: rewrote object table, took 61ms
[16:10:02] [INFO] Function resized and AFTER Hex Installed
[16:10:03] [INFO] Resize: inserted new hex, took 88ms
[16:10:03] [INFO] Resize: rewrote object table, took 61ms
[16:10:03] [INFO] Function resized and BEFORE Hex Installed

"insert new hex" is step 1, copying the file and inserting the new hex (and creating a .bak file in the process).

"rewrite object table" is step 2, updating all of the object table entries.

 

For this example I was resizing XGTacticalGameCore.ModifyStatsByDifficulty, which is object 33967 out of 56228 total.

 

Overall time per apply or revert was on the order of 150-250 ms. Definitely not as fast as HxD, but still reasonably quick. :)

Link to comment
Share on other sites

IMO, before-after style patching has one major flaw: you can't apply the same patch twice with small changes. For example, you made a change, discovered an error, fixed erroneous bytes and want to re-apply the change to check it in-game again. With before-after style you need to either undo previous change first or re-write before section to look for a new hex string. With function lookup + relative offsets you can apply and re-apply changes quickly. And this certainly speeds up mod development for me. :smile:
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...