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."); } } }