KarlitosVII / trilogy-save-editor

A save editor for Mass Effect Trilogy (and Legendary)
https://www.nexusmods.com/masseffectlegendaryedition/mods/20
Other
154 stars 20 forks source link

[ME3LE] Marking bonus powers in the save file does nothing #42

Closed levicki closed 2 years ago

levicki commented 2 years ago

As the title says, once your local_profile file in the Saves directory gets corrupted, loading any save from any career with bonus powers marked as unlocked and avaialble doesn't do anything -- the console for purchasing them in the Medbay is empty.

I guess this is more of a feature request -- there should be a way to edit local_profile as well (old local profile editor for the original ME3 cannot decompress the new file), and set the bonus powers to unlocked.

KarlitosVII commented 2 years ago

Yes, it is intended behavior. Your unlocked bonus powers are stored in the GamerProfile file. The Power plots are just here to enumerate what you have unlocked. There is nothing I can do about it unfortunately.

mirh commented 2 years ago

Feature request to have that decoded then?

levicki commented 2 years ago

@KarlitosVII Are you sure those are stored in %USERPROFILE%\Documents\Bioware\Mass Effect Legendary Edition\TrilogyGameProfile and not in %USERPROFILE%\Documents\Bioware\Mass Effect Legendary Edition\Save\ME3\Local_Profile?

I thought it would be possible to add editing of that file as well.

levicki commented 2 years ago

@KarlitosVII Can you at least point me to any information about what method of compression or obfuscation might have been used by the game when saving those files? From my brief analysis of Local_Profile I see that it contains the name of current character save folder in almost plain text.

For example, my character save folder is Xenia_13_Sentinel_040322_4f74eef and in the Local_Profile the string is Xcnia603_SenrineB^040320_4f 0eef. A little bit further there is a string C_Kovenrward which I assume is actually something like `??MoveForward' (i.e. the keyboard mappings seem to be in there too because I lost them when the file got corrupted).

I have tried figuring out the file format by comparing files after unlocking each power, but it doesn't seem that the whole file is compressed with any standard algorithm (yet the partially mangled strings would suggest some light/fast compression), nor there seem to be any sort of fixed header. Any pointers would be appreciated.

levicki commented 2 years ago

@KarlitosVII I just found this in SFXGame.pcc file in SFXProfileSettings class using Package Editor, maybe it will be useful:

BonusPowerUnlockDataArray = (
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Carnage", BonusPowerID = 1, PlotConditionalID = 1282, PlotStateID = 22113, srTitle = $668831}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Marksman", BonusPowerID = 2, PlotConditionalID = 1278, PlotStateID = 22109, srTitle = $572088}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_ProximityMine", BonusPowerID = 4, PlotConditionalID = 1274, PlotStateID = 22105, srTitle = $572665}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Decoy", BonusPowerID = 8, PlotConditionalID = 1272, PlotStateID = 22103, srTitle = $674631}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_ProtectorDrone", BonusPowerID = 16, PlotConditionalID = 1270, PlotStateID = 22101, srTitle = $663224}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_EnergyDrain", BonusPowerID = 32, PlotConditionalID = 1269, PlotStateID = 22100, srTitle = $205894}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_InfernoGrenade", BonusPowerID = 64, PlotConditionalID = 1277, PlotStateID = 22108, srTitle = $349055}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Reave", BonusPowerID = 128, PlotConditionalID = 1276, PlotStateID = 22107, srTitle = $314878}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Stasis", BonusPowerID = 256, PlotConditionalID = 1280, PlotStateID = 22111, srTitle = $127059}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_WarpAmmo", BonusPowerID = 512, PlotConditionalID = 1279, PlotStateID = 22110, srTitle = $501005}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Barrier", BonusPowerID = 1024, PlotConditionalID = 1275, PlotStateID = 22106, srTitle = $93973}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_GethShieldBoost", BonusPowerID = 2048, PlotConditionalID = 1271, PlotStateID = 22102, srTitle = $314066}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Fortification", BonusPowerID = 4096, PlotConditionalID = 1281, PlotStateID = 22112, srTitle = $314036}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_ArmorPiercingAmmo", BonusPowerID = 8192, PlotConditionalID = 1273, PlotStateID = 22104, srTitle = $93965}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_Slam", BonusPowerID = 16384, PlotConditionalID = 1283, PlotStateID = 22114, srTitle = $542178}, 
    {PowerClassName = "SFXGameContent.SFXPowerCustomAction_DarkChannel", BonusPowerID = 32768, PlotConditionalID = 1284, PlotStateID = 22115, srTitle = $716448}
)

For the power to unlock, PlotStateID has to be set as well.

Power unlocks are stored in the profile settings as a bitmask in a single 32-bit integer:

public final function bool IsBonusPowerUnlocked(int BonusPowerID)
{
    local int UnlockedPowers;

    GetProfileSettingValueInt(96, UnlockedPowers);
    if ((UnlockedPowers & BonusPowerID) == BonusPowerID) {
        return TRUE;
    } else {
        return FALSE;
    }
}

I presume 96 might be the offset in the profile. If they aren't unlocked in the profile, their PlotStateIDs get reset:

public final function UpdateBonusPowerPlotStates()
{
    local int idx;
    local BioWorldInfo WI;
    local BioGlobalVariableTable VarTable;

    WI = BioWorldInfo(Class'WorldInfo'.static.GetWorldInfo());
    if (WI == None) {
        return;
    }
    VarTable = WI.GetGlobalVariables();
    if (VarTable == None) {
        return;
    }
    for (idx = 0; idx < BonusPowerUnlockDataArray.Length; idx++) {
        if (IsBonusPowerUnlocked(BonusPowerUnlockDataArray[idx].BonusPowerID) == TRUE) {
            VarTable.SetBool(BonusPowerUnlockDataArray[idx].PlotStateID, TRUE, FALSE, TRUE);
            continue;
        }
        VarTable.SetBool(BonusPowerUnlockDataArray[idx].PlotStateID, FALSE, FALSE, TRUE);
    }
}

Maybe one day we figure out the profile format as well.