public void ExportToFile(string path) { using (FileStream sds = new FileStream(path, FileMode.Create, FileAccess.ReadWrite)) { long currentPosition; long blockTableOffsetPosition; long xmlOffsetPosition; long headerChecksumPosition; sds.WriteString("SDS", sizeof(uint)); sds.WriteUInt32(Header.Version); sds.WriteString(Header.Platform.ToString(), sizeof(uint)); using (MemoryStream ms = new MemoryStream()) { ms.WriteString("SDS", sizeof(uint)); ms.WriteUInt32(Header.Version); ms.WriteString(Header.Platform.ToString(), sizeof(uint)); sds.WriteUInt32(FNV.Hash32(ms.ReadAllBytes())); } Header.ResourceTypeTableOffset = SdsHeader.HeaderSize; sds.WriteUInt32(Header.ResourceTypeTableOffset); blockTableOffsetPosition = sds.Position; sds.Seek(sizeof(uint), SeekOrigin.Current); xmlOffsetPosition = sds.Position; sds.Seek(sizeof(uint), SeekOrigin.Current); sds.WriteUInt32(SlotRamRequired); sds.WriteUInt32(SlotVRamRequired); sds.WriteUInt32(OtherRamRequired); sds.WriteUInt32(OtherVRamRequired); sds.WriteUInt32(SdsHeader.Unknown32_2C); sds.WriteUInt64((ulong)Header.GameVersion); sds.Seek(sizeof(ulong), SeekOrigin.Current); sds.WriteUInt32((uint)_resources.Count); headerChecksumPosition = sds.Position; sds.Seek(sizeof(uint), SeekOrigin.Current); sds.WriteUInt32((uint)_resourceTypes.Count); foreach (ResourceType resourceType in _resourceTypes) { sds.WriteUInt32(resourceType.Id); sds.WriteUInt32((uint)resourceType.ToString().Length); sds.WriteString(resourceType.ToString()); sds.WriteUInt32(resourceType.Unknown32); } currentPosition = sds.Position; Header.BlockTableOffset = (uint)currentPosition; sds.Seek(blockTableOffsetPosition, SeekOrigin.Begin); sds.WriteUInt32((uint)currentPosition); sds.Seek(currentPosition, SeekOrigin.Begin); sds.WriteUInt32(UEzl); if (Header?.Version == 19) { sds.WriteUInt32(MaxBlockSizeV19); } else if (Header?.Version == 20) { sds.WriteUInt32(MaxBlockSizeV20); } sds.WriteUInt8(4); bool first = true; int blockSize = Header?.Version == 19U ? MaxBlockSizeV19 : MaxBlockSizeV20; foreach (MemoryStream block in MergeDataIntoBlocks(blockSize)) { block.SeekToStart(); if ((first || block.Length >= 10240) && Header?.Version != 20U) { byte[] blockData = block.ReadAllBytes(); #region Version 19 if (Header?.Version == 19) { MemoryStream compressedBlock = new MemoryStream(); ZOutputStream compressStream = new ZOutputStream(compressedBlock, zlibConst.Z_BEST_COMPRESSION); compressStream.Write(blockData, 0, blockData.Length); compressStream.finish(); sds.WriteUInt32((uint)compressStream.TotalOut + 32U); sds.WriteUInt8((byte)EDataBlockType.Compressed); sds.WriteUInt32((uint)block.Length); sds.WriteUInt32(32); sds.WriteUInt32(81920); sds.WriteUInt32(135200769); sds.WriteUInt32((uint)compressStream.TotalOut); sds.WriteUInt64(0); sds.WriteUInt32(0); compressedBlock.SeekToStart(); sds.Write(compressedBlock.ReadAllBytes()); } #endregion #region Version 20 //else if (Header?.Version == 20) //{ // byte[] compressed = Oodle.Compress(blockData, blockData.Length); // sds.WriteUInt32((uint)compressed.Length + 128U); // sds.WriteUInt8((byte)EDataBlockType.Compressed); // sds.WriteUInt32((uint)block.Length); // sds.WriteUInt32(128); // sds.WriteUInt32(65537); // sds.WriteUInt32((uint)block.Length); // sds.WriteUInt32((uint)compressed.Length); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt64(0); // sds.WriteUInt32(0); // sds.Write(compressed); //} #endregion } else { sds.WriteUInt32((uint)block.Length); sds.WriteUInt8((byte)EDataBlockType.Uncompressed); sds.Write(block.ReadAllBytes()); } first = false; } sds.WriteUInt32(0); sds.WriteUInt8(0); if (Header?.Version == 20U) { Header.XmlOffset = 0; sds.Seek(xmlOffsetPosition, SeekOrigin.Begin); sds.WriteUInt32(Header.XmlOffset); } else { currentPosition = sds.Position; Header.XmlOffset = (uint)currentPosition; sds.Seek(xmlOffsetPosition, SeekOrigin.Begin); sds.WriteUInt32((uint)currentPosition); } sds.Seek(headerChecksumPosition, SeekOrigin.Begin); sds.WriteUInt32(FNV.Hash32(Header.ResourceTypeTableOffset, Header.BlockTableOffset, Header.XmlOffset, SlotRamRequired, SlotVRamRequired, OtherRamRequired, OtherVRamRequired, SdsHeader.Unknown32_2C, (ulong)Header.GameVersion, 0UL, (uint)_resources.Count)); if (Header?.Version == 19U) { sds.Seek(currentPosition, SeekOrigin.Begin); sds.WriteString(XmlString); } } }
public static SdsHeader FromFile(string sdsFilePath) { SdsHeader header = new SdsHeader(); header.Name = Path.GetFileName(sdsFilePath); using (FileStream fileStream = new FileStream(sdsFilePath, FileMode.Open, FileAccess.Read)) { if (fileStream.Length < HeaderSize) { throw new InvalidDataException("Invalid file!"); } if (fileStream.ReadString(sizeof(uint)) != "SDS") { throw new InvalidDataException("This file does not contain SDS header!"); } header.Version = fileStream.ReadUInt32(); if (header.Version > MaxSupportedVersion) { throw new NotSupportedException($"Version {header.Version} not supported"); } string platformString = fileStream.ReadString(sizeof(uint)); if (Enum.TryParse(platformString, out EPlatform platform)) { header.Platform = platform; } else { throw new InvalidDataException(platformString); } if (header.Platform != EPlatform.PC) // In future will be added multiplatform support { throw new NotSupportedException($"Platform {platform} not supported"); } uint hash = fileStream.ReadUInt32(); using (MemoryStream ms = new MemoryStream()) { ms.WriteString("SDS", sizeof(uint)); ms.WriteUInt32(header.Version); ms.WriteString(header.Platform.ToString(), sizeof(uint)); var computedHash = FNV.Hash32(ms.ReadAllBytes()); if (computedHash != hash) { throw new InvalidDataException("Checksum difference."); } } header.ResourceTypeTableOffset = fileStream.ReadUInt32(); header.BlockTableOffset = fileStream.ReadUInt32(); header.XmlOffset = fileStream.ReadUInt32(); if (header.Version == 19U && header.XmlOffset == Encrypted) { throw new NotSupportedException("This SDS file is encrypted."); } uint slotRamRequired = fileStream.ReadUInt32(); uint slotVRamRequired = fileStream.ReadUInt32(); uint otherRamRequired = fileStream.ReadUInt32(); uint otherVRamRequired = fileStream.ReadUInt32(); if (fileStream.ReadUInt32() != SdsHeader.Unknown32_2C) { throw new Exception("Bytes do not match."); } header.GameVersion = (EGameVersion)fileStream.ReadUInt64(); if (header.GameVersion != EGameVersion.Classic && header.GameVersion != EGameVersion.DefinitiveEdition) { throw new NotSupportedException(header.GameVersion.ToString()); } // Skipping of null bytes fileStream.Seek(sizeof(ulong), SeekOrigin.Current); uint numberOfFiles = fileStream.ReadUInt32(); uint checksum = fileStream.ReadUInt32(); uint calculatedChecksum = FNV.Hash32(header.ResourceTypeTableOffset, header.BlockTableOffset, header.XmlOffset, slotRamRequired, slotVRamRequired, otherRamRequired, otherVRamRequired, Unknown32_2C, (ulong)header.GameVersion, 0UL, numberOfFiles); if (calculatedChecksum != checksum) { throw new Exception("Checksum difference!"); } } return(header); }