public static SdsFile FromFile(string sdsPath) { SdsFile file = new SdsFile(); file.Path = System.IO.Path.GetFullPath(sdsPath); file.Header = SdsHeader.FromFile(sdsPath); using (FileStream fileStream = new FileStream(sdsPath, FileMode.Open, FileAccess.Read)) { fileStream.Seek(file.Header.ResourceTypeTableOffset, SeekOrigin.Begin); uint numberOfResources = fileStream.ReadUInt32(); for (int i = 0; i < numberOfResources; i++) { ResourceType resourceType = new ResourceType(); resourceType.Id = fileStream.ReadUInt32(); uint resourceLenght = fileStream.ReadUInt32(); string typeStr = fileStream.ReadString((int)resourceLenght).Replace(" ", ""); resourceType.Name = (EResourceType)Enum.Parse(typeof(EResourceType), typeStr); uint unknown32 = fileStream.ReadUInt32(); if (unknown32 != resourceType.Unknown32) { throw new InvalidDataException(unknown32.ToString()); } file.AddResourceType(resourceType); } fileStream.Seek(file.Header.BlockTableOffset, SeekOrigin.Begin); #region Version 19 if (file.Header.Version == 19U) { if (fileStream.ReadUInt32() != UEzl) { throw new Exception("Invalid SDS file!"); } fileStream.Seek(5, SeekOrigin.Current); using (MemoryStream decompressedData = new MemoryStream()) { while (true) { uint blockSize = fileStream.ReadUInt32(); EDataBlockType blockType = (EDataBlockType)fileStream.ReadUInt8(); if (blockSize == 0U) { break; } if (blockType == EDataBlockType.Compressed) { fileStream.Seek(16, SeekOrigin.Current); uint compressedBlockSize = fileStream.ReadUInt32(); if (blockSize - 32U != compressedBlockSize) { throw new Exception("Invalid block!"); } fileStream.Seek(12, SeekOrigin.Current); byte[] compressedBlock = new byte[compressedBlockSize]; fileStream.Read(compressedBlock, 0, compressedBlock.Length); ZOutputStream decompressStream = new ZOutputStream(decompressedData); decompressStream.Write(compressedBlock, 0, compressedBlock.Length); decompressStream.finish(); } else if (blockType == EDataBlockType.Uncompressed) { byte[] decompressedBlock = new byte[blockSize]; fileStream.Read(decompressedBlock, 0, decompressedBlock.Length); decompressedData.Write(decompressedBlock, 0, decompressedBlock.Length); } else { throw new Exception("Invalid block type!"); } } decompressedData.SeekToStart(); fileStream.Seek(file.Header.XmlOffset, SeekOrigin.Begin); byte[] xmlBytes = fileStream.ReadBytes((int)fileStream.Length - (int)fileStream.Position); using (MemoryStream memoryStream = new MemoryStream(xmlBytes)) using (XmlReader xmlReader = XmlReader.Create(memoryStream)) { ResourceInfo resourceInfo = new ResourceInfo(); List <Type> resourceTypes = new List <Type>(); foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) { if (type.BaseType == typeof(Resource)) { resourceTypes.Add(type); } } while (xmlReader.Read()) { if (xmlReader.NodeType == XmlNodeType.Element) { if (xmlReader.Name == "TypeName") { resourceInfo = new ResourceInfo(); var resourceType = (EResourceType)Enum.Parse(typeof(EResourceType), xmlReader.ReadElementContentAsString()); resourceInfo.Type = file._resourceTypes.First(x => x.Name == resourceType); } else if (xmlReader.Name == "SourceDataDescription") { resourceInfo.SourceDataDescription = xmlReader.ReadElementContentAsString(); uint resId = decompressedData.ReadUInt32(); if (resId != resourceInfo.Type.Id) { throw new InvalidDataException(); } uint size = decompressedData.ReadUInt32(); ushort version = decompressedData.ReadUInt16(); uint slotRamRequired = decompressedData.ReadUInt32(); uint slotVRamRequired = decompressedData.ReadUInt32(); uint otherRamRequired = decompressedData.ReadUInt32(); uint otherVRamRequired = decompressedData.ReadUInt32(); uint checksum = decompressedData.ReadUInt32(); byte[] rawData = decompressedData.ReadBytes((int)size - Resource.StandardHeaderSizeV19); var targetType = resourceTypes.First(x => x.Name == resourceInfo.Type.ToString()); var deserializeMethod = targetType.GetMethod(nameof(Resource.Deserialize)); var resourecInstance = (Resource)deserializeMethod.Invoke(null, new object[] { resourceInfo, version, slotRamRequired, slotVRamRequired, otherRamRequired, otherVRamRequired, null, rawData, file._mapper }); file._resources.Add(resourecInstance); } } } } } } #endregion #region Version 20 else if (file.Header.Version == 20U) { if (fileStream.ReadUInt32() != UEzl) { throw new Exception("Invalid SDS file!"); } fileStream.Seek(5, SeekOrigin.Current); using (MemoryStream decompressedData = new MemoryStream()) { while (true) { uint blockSize = fileStream.ReadUInt32(); EDataBlockType blockType = (EDataBlockType)fileStream.ReadUInt8(); if (blockSize == 0U) { break; } if (blockType == EDataBlockType.Compressed) { uint uncompressedSize = fileStream.ReadUInt32(); fileStream.Seek(12, SeekOrigin.Current); uint compressedBlockSize = fileStream.ReadUInt32(); if (blockSize - 128U != compressedBlockSize) { throw new Exception("Invalid block!"); } fileStream.Seek(108, SeekOrigin.Current); byte[] compressedBlock = new byte[compressedBlockSize]; fileStream.Read(compressedBlock, 0, compressedBlock.Length); byte[] decompressed = Oodle.Decompress(compressedBlock, compressedBlock.Length, (int)uncompressedSize); decompressedData.Write(decompressed); } else if (blockType == EDataBlockType.Uncompressed) { byte[] decompressedBlock = new byte[blockSize]; fileStream.Read(decompressedBlock, 0, decompressedBlock.Length); decompressedData.Write(decompressedBlock, 0, decompressedBlock.Length); } else { throw new Exception("Invalid block type!"); } } List <Type> resourceTypes = new List <Type>(); foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) { if (type.BaseType == typeof(Resource)) { resourceTypes.Add(type); } } decompressedData.SeekToStart(); while (decompressedData.Position != decompressedData.Length) { uint resId = decompressedData.ReadUInt32(); ResourceInfo resourceInfo = new ResourceInfo(); resourceInfo.Type = file._resourceTypes.First(x => x.Id == resId); uint size = decompressedData.ReadUInt32(); ushort version = decompressedData.ReadUInt16(); ulong nameHash = decompressedData.ReadUInt64(); uint slotRamRequired = decompressedData.ReadUInt32(); uint slotVRamRequired = decompressedData.ReadUInt32(); uint otherRamRequired = decompressedData.ReadUInt32(); uint otherVRamRequired = decompressedData.ReadUInt32(); uint checksum = decompressedData.ReadUInt32(); byte[] rawData = decompressedData.ReadBytes((int)size - Resource.StandardHeaderSizeV20); var targetType = resourceTypes.First(x => x.Name == resourceInfo.Type.ToString()); var deserializeMethod = targetType.GetMethod(nameof(Resource.Deserialize)); var resourceInstance = (Resource)deserializeMethod.Invoke(null, new object[] { resourceInfo, version, slotRamRequired, slotVRamRequired, otherRamRequired, otherVRamRequired, nameHash, rawData, file._mapper }); file._resources.Add(resourceInstance); } } } #endregion } return(file); }
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); }