void ReadSaveData(Savegame savegame, IDataReader dataReader)
        {
            savegame.Year               = dataReader.ReadWord();
            savegame.Month              = dataReader.ReadWord();
            savegame.DayOfMonth         = dataReader.ReadWord();
            savegame.Hour               = dataReader.ReadWord();
            savegame.Minute             = dataReader.ReadWord();
            savegame.CurrentMapIndex    = dataReader.ReadWord();
            savegame.CurrentMapX        = dataReader.ReadWord();
            savegame.CurrentMapY        = dataReader.ReadWord();
            savegame.CharacterDirection = (CharacterDirection)dataReader.ReadWord();

            // Active spells (2 words each)
            // First word: duration in 5 minute chunks. So 120 means 120 * 5 minutes = 600 minutes = 10h
            // Second word: level (e.g. for light: 1 = magic torch, 2 = magic lantern, 3 = magic sun)
            // An active light spell replaces an existing one.
            // The active spells are in fixed spots:
            // - 0: Light (candle)
            // - 1: Magic barrier (shield)
            // - 2: Magic attack (sword)
            // - 3: Anti-magic barrier (star)
            // - 4: Clairvoyance (eye)
            // - 5: Magic map (map)
            foreach (var activeSpellType in Enum.GetValues <ActiveSpellType>())
            {
                var duration = dataReader.ReadWord();

                if (duration == 0)
                {
                    savegame.ActiveSpells[(int)activeSpellType] = null;
                    dataReader.Position += 2;
                }
                else
                {
                    savegame.ActivateSpell(activeSpellType, duration, dataReader.ReadWord());
                }
            }

            dataReader.ReadWord();                                      // Number of party members. We don't really need it.
            savegame.ActivePartyMemberSlot = dataReader.ReadWord() - 1; // it is stored 1-based

            for (int i = 0; i < 6; ++i)
            {
                savegame.CurrentPartyMemberIndices[i] = dataReader.ReadWord();
            }

            savegame.YearsPassed        = dataReader.ReadWord();
            savegame.TravelType         = (TravelType)dataReader.ReadWord();
            savegame.SpecialItemsActive = dataReader.ReadWord();
            savegame.GameOptions        = dataReader.ReadWord();
            savegame.HoursWithoutSleep  = dataReader.ReadWord();

            // up to 32 transport positions
            for (int i = 0; i < 32; ++i)
            {
                var type = dataReader.ReadByte();

                if (type == 0)
                {
                    savegame.TransportLocations[i] = null;
                    dataReader.Position           += 5;
                }
                else
                {
                    var x = dataReader.ReadByte();
                    var y = dataReader.ReadByte();
                    dataReader.Position += 1; // unknown byte
                    var mapIndex = dataReader.ReadWord();

                    savegame.TransportLocations[i] = new TransportLocation
                    {
                        TravelType = (TravelType)type,
                        MapIndex   = mapIndex,
                        Position   = new Position(x, y)
                    };
                }
            }


            // global variables (at offset 0x0104, 1024 bytes = 8192 bits = 8192 variables)
            // note that wind gate repair status is also handled by global variables (at offset 0x0112)
            Buffer.BlockCopy(dataReader.ReadBytes(1024), 0, savegame.GlobalVariables, 0, 1024);

            // map event bits. each bit stands for a event. order is 76543210 FECDBA98 ...
            for (int i = 0; i < 1024; ++i)
            {
                savegame.MapEventBits[i] = dataReader.ReadQword();
            }

            // character event bits. each bit stands for a character. order is 76543210 FECDBA98 ...
            for (int i = 0; i < 1024; ++i)
            {
                savegame.CharacterBits[i] = dataReader.ReadDword();
            }

            savegame.DictionaryWords   = dataReader.ReadBytes(128);
            savegame.GotoPointBits     = dataReader.ReadBytes(32); // 32 bytes for goto points (256 bits). Each goto point stores the bit index (0-based).
            savegame.ChestUnlockStates = dataReader.ReadBytes(32); // 32 * 8 bits = 256 bits (1 for each chest, possible chest indices 0 to 255)
            savegame.DoorUnlockStates  = dataReader.ReadBytes(32); // 32 * 8 bits = 256 bits (1 for each door, possible door indices 0 to 255)

            Buffer.BlockCopy(dataReader.ReadBytes(6), 0, savegame.BattlePositions, 0, 6);

            savegame.TileChangeEvents.Clear();

            while (dataReader.Position < dataReader.Size)
            {
                var mapIndex = dataReader.ReadWord();

                if (mapIndex == 0) // end
                {
                    break;
                }

                var x         = dataReader.ReadByte();
                var y         = dataReader.ReadByte();
                var tileIndex = dataReader.ReadWord();

                if (mapIndex != 0 && mapIndex < 0x0300 && tileIndex <= 0xffff) // should be a real map and a valid front tile index
                {
                    savegame.TileChangeEvents.SafeAdd(mapIndex, new ChangeTileEvent
                    {
                        Type           = EventType.ChangeTile,
                        Index          = uint.MaxValue,
                        MapIndex       = mapIndex,
                        X              = x,
                        Y              = y,
                        FrontTileIndex = tileIndex
                    });
                }
                else
                {
                    throw new AmbermoonException(ExceptionScope.Data, "Invalid tile change event in savegame.");
                }
            }
        }