kalypso media :: forum

Full Version: Editing a dynasty's save file
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Anyone check out the dynasty save files?
The entries for the individuals look like:

        costume = {
            Accessory = "Avatar_Glasses_F_03",
            Entity = "Avatar_Costume_F_Cm_As",
            Hair = {
            HairColor = -15658477,
            Hat = false,
            Head = "Avatar_Head_F_As_01",
            MainColor = -11204075,
            SupplementaryColor = -14805946,
        disappeared = false,
        facts = {},
        family_name = "McNally",
        first_name = "Segunda",
        gender = "female",
        image_data =

Then a bunch of binary noise in " " representing the dynasty member's portrait, then:

        is_president = false,
        level = 5,
        race = "As",
        skill = "Environmentalist",
        unique_id = 11,
        win_mission = false,

Now, some of the values can be changed successfully. I've been able to modify the level field (a minor time saver) and the face of the members. This is helpful if you get a really ugly member with a good skill so you don't have to kill her after all. However, whenever I make a change that affects the number of characters in the file, the game says it's corrupt.

For example, I can change Aldo's name to a different four letter word:

[Image: 67tmdy.jpg]

But if I change it to something of a different length, it tells me it's corrupt:

[Image: 2rr2wio.jpg]

There is clearly a byte-count somewhere that's used to tell if the file is corruption-clear. But it's plainly not in plaintext.

I have a feeling that the "BPUL\ [bla bla nullchars]" means something like "Big phat stupid unsigned long integer" in Lua, and the "null" characters in the text editor are part of the byte count. Can anyone skilled in Lua help?

Does anyone have ideas on how to mod the byte count in the file?

If so, there's an open door for modding everything about your dynasty. No more arbitrary rules about naming and looks.
I would really like to make my own custom dynasty...
Made some progress.

I glanced through those curious null characters in hex view and noticed a 32-bit chunk, 28 bytes into the file, which was different in each file, while the bytes around it stayed the same. I wrote a little C# app to analyze those bytes in all my save files, and compare it to the size of the file (to see if that chunk represents the count of bytes.) Here is what I found:

1402858873.dyn.sav  File Length=90090,  Read Length=89898,  Difference=192
1402865851.dyn.sav  File Length=87895,  Read Length=87703,  Difference=192
1402823762.dyn.sav  File Length=544594,  Read Length=544402,  Difference=192
1402518057.dyn.sav  File Length=620061,  Read Length=619869,  Difference=192

1402443100.sav  File Length=6692339,  Read Length=260319,  Difference=6432020
1402457572.sav  File Length=6908315,  Read Length=384628,  Difference=6523687
1402518057.sav  File Length=5343943,  Read Length=230990,  Difference=5112953
1402622069.sav  File Length=7915663,  Read Length=549540,  Difference=7366123
1402699185.sav  File Length=6601890,  Read Length=417192,  Difference=6184698
1402809838.sav  File Length=6127990,  Read Length=21104,  Difference=6106886
1402814329.sav  File Length=3885283,  Read Length=330692,  Difference=3554591
1402817672.auto.sav  File Length=3830776,  Read Length=332376,  Difference=3498400
1402819139.auto.sav  File Length=3807818,  Read Length=761817,  Difference=3046001
1402823762.sav  File Length=7311749,  Read Length=249026,  Difference=7062723

The "Read Length" field is the number stored in the curious 32-bit chunk in each file. I've separated the dynasty files (ending in ".dyn.sav") and it does appear that there is a pattern with this number. As you can see, the number is always 192 less than the file length, even though the file lengths vary greatly!

The other save files (map saves) do not follow this pattern. Perhaps they use a 64-bit integer instead of a 32-bit one? That would make sense since map saves are much larger, containing all the data of an island. A pattern may become visible if I check for a 64-bit chunk. I might do that later.
EDIT: Actually, the map saves would have to be over 4GB to need a 64-bit byte counter... that doesn't mean they didn't happen to store it in a different number of bits, or at a different offset.

The next thing to work on is to change the magic number & see if it allows me to make more extensive modifications to the dynasty saves. Smile

Here is the source for the little C# program, just 'cause.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CountAdjuster
    class Program
        static void Main(string[] args)
            using (StreamWriter writer = new StreamWriter("result.txt", false))
                foreach (var file in Directory.EnumerateFiles(Directory.GetCurrentDirectory())
                .Where(s => Path.GetExtension(s) == ".sav")
                .Select(f => new FileInfo(f)))
                    int byteCount;

                    using (BinaryReader b = new BinaryReader(file.OpenRead()))
                        if (new string(b.ReadChars(5)) != @"BPUL\") throw new ApplicationException("Unexpected file format");
                        b.ReadBytes(28 - 5);
                        byteCount = b.ReadInt32();

                    writer.WriteLine(String.Format("{0}  File Length={1},  Read Length={2},  Difference={3}",
                        file.Length - byteCount));
As for now you can just take away text from the biography to make the length and size match again.
Well, modifying that number is easy. It doesn't work though. I'm betting there's another count in the footer just in case the header is damaged.
More progress. The number I found in the header is a pointer (offset from the beginning of the file) to another 32-bit pointer in the footer. The pointer in the footer simply refers to the location just after the last "}" of the dynasty declarations. The pointer in the footer appears to always be the file length minus 235. Having these two numbers be correct prevents the game from displaying a "file corrupt" error message. However, the file still cannot be loaded. I will have to examine the footers more closely after I nap & stuff.

[Image: jal30g.jpg]

The only clue I have right now is that maybe the "5C" that is in the same place in the footer of each file, is a pointer back to the beginning of the file (the "return{[dynastystuff]" starts on 5C). Perhaps the game is failing to parse and display the list of dynasty members because modifications make it unable to find the "5C" pointer back up to the top.
It would be way easier if a dev cleared the shit.
More progress. I've analyzed literally every byte in the header & footer of the dynasty save files I've been dissecting. As mentioned in my previous posts, I've figured out some ratios that need to be in place to make the game (partly) happy.

There are exactly three 32-bit values left to figure out. Once I discover their significance, it should be possible to make extensive modifications to dynasties (or, potentially, create a new dynasty from scratch!)

In the C# code below, you can read the notes I've taken. I've written down the location of every significant byte in the header and footer of those save files. This class will turn into the model I will use to represent the saves in code.

class DynastySaveFile
        // The save files begin with this text
        private const string SAV_PREFIX_TEXT = @"BPUL\";

        // At offset 12 there is always 0xFFFFFFFF
        // At offset 24 there is always 0x01

        // At offset 28, a 32-bit value indicates the offset to the footer
        // Its value is always equal to the file length minus 192 (or end-offset 192)
        private int _footerOffset;

        // At end-offset 224 is always the text "metadata.lua" followed by 0x03
        // At end-offset 204 is always 0x0B
        // At end-offset 202 is always the text "dynasty.lua"

        // At end-offset 192 is a 32-bit value equals the offset that the Lua ends on (the byte after "}", which seems to always be 0x02)
        private int _luaEndOffset;

        // Value stored at end-offset 127 (is always 0x5C, 92 decimal) equals the offset that the Lua starts on (the letter "r")
        private int _luaStartOffset;

        // Value stored at end-offset 123
        private int _unknownFooterValue_1;

        // Value stored at end-offset 64
        private int _unknownFooterValue_2;

        // Value stored at end-offset 60
        private int _unknownFooterValue_3;
I don't know if you're still working on this, but here I am 4 years later and I decided to play the game. Apparently I played it on a free weekend a long time ago, and made a really dumb dynasty name so I want to change it. I'm trying to figure out where the game gets the dynasty info from though? I set procmon to watch for file access in the player save data folder, but it never touches the *.dyn.sav file, only the *.sav file in question that I was reloading from. Unless I'm missing something? Like is dynasty data stored within sav files?

Edit: When I just manually saved my game, I got a prompt saying that my dynasty was either deleted or something else, and prompted me to rename it. Having done so, their old name persists for some reason...