static void ReadHeader_Postfix(string __0, bool __1, ref GameSaveHeader header, object __3) { if (header != null) { ((CompressionGameSaveHeader)header).IsCompressed = IsCompressedSave; } }
public GameSave(string fileName) { Bytes = File.ReadAllBytes(FileName = fileName); IsRemaster = BitConverter.ToInt32(Bytes, 8) == 0; SaveType = IsRemaster switch { true when Path.GetExtension(fileName) is "" => SaveType.Switch, true => SaveType.Remaster, false => SaveType.Original, }; var pattern = SaveType == SaveType.Switch ? SwitchFilePattern : RemasterFilePattern; if (IsRemaster && Path.GetExtension(fileName) != ".bin" && !Path.GetFileNameWithoutExtension(fileName).StartsWith(pattern)) { throw new NotSupportedException($"Save file is not a user save and changing them can lead to the game infinite looping. The editor only supports saves that start with {pattern}."); } _header = new(this); Buffs = FilterToApplicable(Amalur.Buffs.Values, _header.IsFateswornAware); ItemDefinitions = FilterToApplicable(Amalur.ItemDefinitions.Values, _header.IsFateswornAware); if (BitConverter.ToInt32(Bytes, BodyStart) == CompressedFlag) { Body = new byte[BitConverter.ToInt32(Bytes, BodyStart + 4)]; if (Body.Length != _header.BodyDataLength) { throw new NotSupportedException($"Save file appears corrupted. The header states that the body should have {_header.BodyDataLength} bytes, but the decompressed size is {Body.Length}"); } var bundleInfoStart = BodyStart + 12; var bundleInfoSize = BitConverter.ToInt32(Bytes, bundleInfoStart - 4); using var bundleInfoData = new ZLibStream(new MemoryStream(Bytes, bundleInfoStart, bundleInfoSize), CompressionMode.Decompress); var endOfBundle = bundleInfoData.ReadAll(Body); var gameStateStart = bundleInfoStart + bundleInfoSize + 4; var gameStateSize = BitConverter.ToInt32(Bytes, gameStateStart - 4); using var gameStateData = new ZLibStream(new MemoryStream(Bytes, gameStateStart, gameStateSize), CompressionMode.Decompress); gameStateData.ReadAll(Body.AsSpan(endOfBundle, Body.Length - endOfBundle)); } else { Body = Bytes.AsSpan(BodyStart, BodyDataLength).ToArray(); } _originalBodyLength = Body.Length; Stash = Stash.TryCreateStash(this); ReadOnlySpan <byte> data = Body; _bagOffset = GetBagOffset(data); _gameStateStartOffset = data.IndexOf(new byte[5] { 0xF7, 0x5D, 0x3C, 0x00, 0x0A }); var typeSectionOffset = data.IndexOf(new byte[5] { 0x23, 0xCC, 0x58, 0x00, 0x06 }) is int ix and > -1 ? ix : data.IndexOf(new byte[5] { 0x23, 0xCC, 0x58, 0x00, 0x04 }) is int pix and > -1 ? pix : data.IndexOf(new byte[5] { 0x23, 0xCC, 0x58, 0x00, 0x03 }); _dataLengthOffsets = new[] { _gameStateStartOffset + 5, // gameStateSize data.IndexOf(new byte[5] { 0x0C, 0xAE, 0x32, 0x00, 0x00 }) + 5, // unknown length 1 typeSectionOffset + 5, // type section length }; _itemContainer = new(this, data.IndexOf(new byte[5] { 0xD3, 0x34, 0x43, 0x00, 0x00 }), 0x00_24_D5_68_00_00_00_0Bul); _itemBuffsContainer = new(this, data.IndexOf(new byte[5] { 0xBB, 0xD5, 0x43, 0x00, 0x00 }), 0x00_28_60_84_00_00_00_0Bul); _itemSocketsContainer = new(this, data.IndexOf(new byte[5] { 0x93, 0xCC, 0x80, 0x00, 0x00 }), 0x00_59_36_38_00_00_00_0Bul); var itemLocs = _itemContainer.ToDictionary(x => x.id, x => (x.offset, x.dataLength)); var itemBuffsLocs = _itemBuffsContainer.ToDictionary(x => x.id, x => (x.offset, x.dataLength)); var itemSocketsLocs = _itemSocketsContainer.ToDictionary(x => x.id, x => (x.offset, x.dataLength)); int dataLength, playerActor = 0; var candidates = new List <(int id, int typeIdOffset, QuestItemDefinition?questItemDef)>(); for (int ixOfActor = _dataLengthOffsets[^ 1] + 4; BitConverter.ToInt32(Body, ixOfActor) == 0x00_75_2D_06; ixOfActor += dataLength)