using System; using System.IO; using System.Text; using SabreTools.Data.Models.STFS; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; namespace SabreTools.Serialization.Readers { public class STFS : BaseBinaryReader { /// public override Volume? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; // Simple check for a valid stream length if (Constants.MinimumHeaderSize > data.Length - data.Position) return null; try { // Cache the current offset long initialOffset = data.Position; // Create a new Volume to fill var volume = new Volume(); // Read and validate the header var header = ParseHeader(data); if (header is null) return null; volume.Header = header; // TODO: Parse the hash table blocks // Don't parse the data blocks into memory return volume; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into a Header /// /// Stream to parse /// Filled Header on success, null on error public static Header? ParseHeader(Stream data) { var obj = new Header(); obj.MagicBytes = data.ReadBytes(4); var signature = System.Text.Encoding.ASCII.GetString(obj.MagicBytes); bool remoteSigned = signature.Equals(Constants.MagicStringLIVE) | signature.Equals(Constants.MagicStringPIRS); if (!remoteSigned && !signature.Equals(Constants.MagicStringCON)) return null; obj.Signature = ParseSignature(data, remoteSigned); obj.LicensingData = ParseLicensingData(data); obj.HeaderHash = data.ReadBytes(20); obj.HeaderSize = data.ReadUInt32BigEndian(); obj.ContentType = data.ReadInt32BigEndian(); obj.MetadataVersion = data.ReadInt32BigEndian(); obj.ContentSize = data.ReadInt64BigEndian(); obj.MediaID = data.ReadUInt32BigEndian(); obj.Version = data.ReadInt32BigEndian(); obj.BaseVersion = data.ReadInt32BigEndian(); obj.TitleID = data.ReadUInt32BigEndian(); obj.Platform = data.ReadByteValue(); obj.ExecutableType = data.ReadByteValue(); obj.DiscNumber = data.ReadByteValue(); obj.DiscInSet = data.ReadByteValue(); obj.SaveGameID = data.ReadUInt32BigEndian(); obj.ConsoleID = data.ReadBytes(5); obj.ProfileID = data.ReadBytes(8); // Peek forward to read whether VolumeDescriptor is SVOD or STFS byte[] peeked = data.PeekBytes(52); bool svod = peeked[51] == 0x01; obj.VolumeDescriptor = ParseVolumeDescriptor(data, svod); obj.DataFileCount = data.ReadInt32BigEndian(); obj.DataFileCombinedSize = data.ReadInt64BigEndian(); obj.DescriptorType = data.ReadUInt32BigEndian(); obj.Reserved = data.ReadUInt32BigEndian(); if (obj.MetadataVersion == 2) { obj.SeriesID = data.ReadBytes(16); obj.SeasonID = data.ReadBytes(16); obj.SeasonNumber = data.ReadInt16BigEndian(); obj.EpisodeNumber = data.ReadInt16BigEndian(); obj.Padding = data.ReadBytes(40); } else { obj.Padding = data.ReadBytes(76); } obj.DeviceID = data.ReadBytes(20); obj.DisplayName = data.ReadBytes(2304); obj.DisplayDescription = data.ReadBytes(2304); obj.PublisherName = data.ReadBytes(128); obj.TitleName = data.ReadBytes(128); obj.TransferFlags = data.ReadByteValue(); obj.ThumbnailImageSize = data.ReadInt32BigEndian(); obj.TitleThumbnailImageSize = data.ReadInt32BigEndian(); if (obj.MetadataVersion == 2) { obj.ThumbnailImage = data.ReadBytes(0x3D00); obj.AdditionalDisplayNames = data.ReadBytes(768); obj.TitleThumbnailImage = data.ReadBytes(0x3D00); obj.AdditionalDisplayDescriptions = data.ReadBytes(768); } else { obj.ThumbnailImage = data.ReadBytes(0x4000); obj.TitleThumbnailImage = data.ReadBytes(0x4000); } // Parse optional header if header size (rounded up to nearest block) is sufficiently large if (((obj.HeaderSize + 0xFFF) & 0xFFFFF000) - Constants.MinimumHeaderSize >= 0x15F4) { byte[] installerType = data.ReadBytes(4); string type = Encoding.UTF8.GetString(installerType); if (type.Equals(Constants.InstallerTypeSystemUpdate) || type.Equals(Constants.InstallerTypeTitleUpdate)) { var updateHeader = new InstallerUpdateHeader(); updateHeader.InstallerType = installerType; updateHeader.InstallerBaseVersion = data.ReadUInt32BigEndian(); updateHeader.InstallerVersion = data.ReadUInt32BigEndian(); obj.InstallerHeader = updateHeader; } else if (type.Equals(Constants.InstallerTypeSystemUpdateCache) || type.Equals(Constants.InstallerTypeTitleUpdateCache) || type.Equals(Constants.InstallerTypeTitleContentCache)) { var cacheHeader = new InstallerCacheHeader(); cacheHeader.InstallerType = installerType; cacheHeader.ResumeState = data.ReadUInt32BigEndian(); cacheHeader.CurrentFileIndex = data.ReadUInt64BigEndian(); cacheHeader.BytesProcessed = data.ReadUInt64BigEndian(); cacheHeader.LastModifiedDateTime = data.ReadInt64BigEndian(); cacheHeader.ResumeData = data.ReadBytes(5584); obj.InstallerHeader = cacheHeader; } else if (Array.Exists(installerType, x => x != 0)) { var installerHeader = new InstallerHeader(); installerHeader.InstallerType = installerType; obj.InstallerHeader = installerHeader; } } return obj; } /// /// Parse a Stream into a Signature /// /// Stream to parse /// Filled Signature public static Signature ParseSignature(Stream data, bool remoteSigned) { if (remoteSigned) { var obj = new MicrosoftSignature(); obj.PackageSignature = data.ReadBytes(256); obj.Padding = data.ReadBytes(296); return obj; } else { var obj = new ConsoleSignature(); obj.CertificateSize = data.ReadUInt16BigEndian(); obj.ConsoleID = data.ReadBytes(5); obj.PartNumber = data.ReadBytes(20); obj.ConsoleType = data.ReadByteValue(); obj.CertificateDate = data.ReadBytes(8); obj.PublicExponent = data.ReadBytes(4); obj.PublicModulus = data.ReadBytes(128); obj.CertificateSignature = data.ReadBytes(256); obj.Signature = data.ReadBytes(128); return obj; } } /// /// Parse a Stream into an array of LicenseEntry /// /// Stream to parse /// Filled array of LicenseEntry public static LicenseEntry[] ParseLicensingData(Stream data) { var obj = new LicenseEntry[16]; for (int i = 0; i < 16; i++) { obj[i] = new LicenseEntry(); obj[i].LicenseID = data.ReadInt64BigEndian(); obj[i].LicenseBits = data.ReadInt32BigEndian(); obj[i].LicenseFlags = data.ReadInt32BigEndian(); } return obj; } /// /// Parse a Stream into a VolumeDescriptor /// /// Stream to parse /// Filled VolumeDescriptor public static VolumeDescriptor ParseVolumeDescriptor(Stream data, bool svod) { if (svod) { var obj = new SVODDescriptor(); obj.VolumeDescriptorSize = data.ReadByteValue(); obj.BlockCacheElementCount = data.ReadByteValue(); obj.WorkerThreadProcessor = data.ReadByteValue(); obj.WorkerThreadPriority = data.ReadByteValue(); obj.Hash = data.ReadBytes(20); obj.DeviceFeatures = data.ReadByteValue(); obj.DataBlockCount = data.ReadUInt24LittleEndian(); obj.DataBlockOffset = data.ReadUInt24LittleEndian(); obj.Padding = data.ReadBytes(5); return obj; } else { var obj = new STFSDescriptor(); obj.VolumeDescriptorSize = data.ReadByteValue(); obj.Reserved = data.ReadByteValue(); obj.BlockSeparation = data.ReadByteValue(); obj.FileTableBlockCount = data.ReadInt16LittleEndian(); obj.FileTableBlockNumber = data.ReadInt24LittleEndian(); obj.TopHashTableHash = data.ReadBytes(20); obj.TotalAllocatedBlockCount = data.ReadInt32BigEndian(); obj.TotalUnallocatedBlockCount = data.ReadInt32BigEndian(); return obj; } } } }