Skip to content

Research

Robert Jordan edited this page Jun 5, 2022 · 3 revisions

This page is for research and understanding of the Pokemon save file formats. Basically anything that is still not fully understood or located.


XD: Gale of Darkness

XD: Playtime field

It's assumed that this field is somewhere at offset 48-or-49, size 4-or-8 in the GameConfigData (ID:0) data structure.

This structure is loaded by GCSaveData.LoadXD.

When comparing numerous saves against each other, this field always seems to be incrementing, the only unclear exception is the high bit/byte at offset 48 (which is either 0x40 or 0x80).

Colosseum also uses a field in the GameConfigData data structure at offset 40 (see here) to determine playtime.

Format theories

Epoch: The format is not an absolute playtime for the current save, but has other information included, such as the date the save was made at, or some time that must be subtracted (like with Colosseum).

Modulus: The format stores multiple points of information (e.g. seconds, minutes, day, etc.) in the same fields using multiplication and modulus to set and get the data.

Individual: The bytes of interest contain multiple unrelated fields that are either separated on byte or bit boundaries.

Scaled: The time units are not stored in an expected number, but may require multiplication or division to reach something usable (like with Colosseum).

Leading theory

The format is possibly stored in seconds, at offset 40, as a double-precision float (Big Endian), where the high bit is completely ignored (treated as zero). Alternatively the high bit is just insignificant, as the only save observed with it had a value approaching zero (-2.3738365646881104e-306), and it's assumed this save was created as the first for a new game. Meaning the value would represent no play time.

All times converted this way seem realistic, with realistic hours for play time. The only concern is how most times have a very low difference between previous saves, often 1.5 minutes. 1m30s doesn't seem like nearly enough time for even a Pokemon battle, especially one with snagging involved.

Changing the playtime property:

Note: BigEndian.ToDouble and BigEndian.WriteDouble must be implemented.

public TimeSpan PlayTime {
    get {
        if (gameSave.GameType == GameTypes.Colosseum) {
            return TimeSpan.FromSeconds((double)(BigEndian.ToUInt32(raw, 40) - 0x47000000) / 128);
        }
        else {
            //byte hibit = (byte)(this.gameConfigData.raw[48] & 0x80);
            //this.gameConfigData.raw[48] &= 0x7f;
            TimeSpan time = TimeSpan.FromSeconds(BigEndian.ToDouble(this.gameConfigData.raw, 48));
            //this.gameConfigData.raw[48] |= hibit;
            return time;
        }
    }
    set {
        if (gameSave.GameType == GameTypes.Colosseum) {
            BigEndian.WriteUInt32((uint)(value.TotalSeconds * 128) + 0x47000000, raw, 40);
        }
        else {
            //byte hibit = (byte)(this.gameConfigData.raw[48] & 0x80);
            BigEndian.WriteDouble(value.TotalSeconds, this.gameConfigData.raw, 48);
            //this.gameConfigData.raw[48] |= hibit;
        }
    }
}
Clone this wiki locally