From 525c75092a1d340ea4ba0d7089bbba5aa4bce259 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Sep 2023 22:55:06 -0400 Subject: [PATCH] Add and convert PFF --- Bytes/PFF.Deserializer.cs | 28 ++++++ Streams/PFF.Deserializer.cs | 182 ++++++++++++++++++++++++++++++++++++ Streams/PFF.Serializer.cs | 16 ++++ 3 files changed, 226 insertions(+) create mode 100644 Bytes/PFF.Deserializer.cs create mode 100644 Streams/PFF.Deserializer.cs create mode 100644 Streams/PFF.Serializer.cs diff --git a/Bytes/PFF.Deserializer.cs b/Bytes/PFF.Deserializer.cs new file mode 100644 index 00000000..2c344005 --- /dev/null +++ b/Bytes/PFF.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.PFF; + +namespace SabreTools.Serialization.Bytes +{ + public partial class PFF : IByteSerializer + { + /// +#if NET48 + public Archive Deserialize(byte[] data, int offset) +#else + public Archive? Deserialize(byte[]? data, int offset) +#endif + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return new Streams.PFF().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/PFF.Deserializer.cs b/Streams/PFF.Deserializer.cs new file mode 100644 index 00000000..3a7bb71f --- /dev/null +++ b/Streams/PFF.Deserializer.cs @@ -0,0 +1,182 @@ +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.PFF; +using static SabreTools.Models.PFF.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class PFF : IStreamSerializer + { + /// +#if NET48 + public Archive Deserialize(Stream data) +#else + public Archive? Deserialize(Stream? data) +#endif + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new archive to fill + var archive = new Archive(); + + #region Header + + // Try to parse the header + var header = ParseHeader(data); + if (header == null) + return null; + + // Set the archive header + archive.Header = header; + + #endregion + + #region Segments + + // Get the segments + long offset = header.FileListOffset; + if (offset < 0 || offset >= data.Length) + return null; + + // Seek to the segments + data.Seek(offset, SeekOrigin.Begin); + + // Create the segments array + archive.Segments = new Segment[header.NumberOfFiles]; + + // Read all segments in turn + for (int i = 0; i < header.NumberOfFiles; i++) + { + var file = ParseSegment(data, header.FileSegmentSize); + if (file == null) + return null; + + archive.Segments[i] = file; + } + + #endregion + + #region Footer + + // Get the footer offset + offset = header.FileListOffset + (header.FileSegmentSize * header.NumberOfFiles); + if (offset < 0 || offset >= data.Length) + return null; + + // Seek to the footer + data.Seek(offset, SeekOrigin.Begin); + + // Try to parse the footer + var footer = ParseFooter(data); + if (footer == null) + return null; + + // Set the archive footer + archive.Footer = footer; + + #endregion + + return archive; + } + + /// + /// Parse a Stream into a header + /// + /// Stream to parse + /// Filled header on success, null on error + private static Header ParseHeader(Stream data) + { + // TODO: Use marshalling here instead of building + Header header = new Header(); + + header.HeaderSize = data.ReadUInt32(); + byte[] signature = data.ReadBytes(4); + header.Signature = Encoding.ASCII.GetString(signature); + header.NumberOfFiles = data.ReadUInt32(); + header.FileSegmentSize = data.ReadUInt32(); + switch (header.Signature) + { + case Version0SignatureString: + if (header.FileSegmentSize != Version0HSegmentSize) + return null; + break; + + case Version2SignatureString: + if (header.FileSegmentSize != Version2SegmentSize) + return null; + break; + + // Version 3 can sometimes have Version 2 segment sizes + case Version3SignatureString: + if (header.FileSegmentSize != Version2SegmentSize && header.FileSegmentSize != Version3SegmentSize) + return null; + break; + + case Version4SignatureString: + if (header.FileSegmentSize != Version4SegmentSize) + return null; + break; + + default: + return null; + } + + header.FileListOffset = data.ReadUInt32(); + + return header; + } + + /// + /// Parse a Stream into a footer + /// + /// Stream to parse + /// Filled footer on success, null on error + private static Footer ParseFooter(Stream data) + { + // TODO: Use marshalling here instead of building + Footer footer = new Footer(); + + footer.SystemIP = data.ReadUInt32(); + footer.Reserved = data.ReadUInt32(); + byte[] kingTag = data.ReadBytes(4); + footer.KingTag = Encoding.ASCII.GetString(kingTag); + + return footer; + } + + /// + /// Parse a Stream into a file entry + /// + /// Stream to parse + /// PFF segment size + /// Filled file entry on success, null on error + private static Segment ParseSegment(Stream data, uint segmentSize) + { + // TODO: Use marshalling here instead of building + Segment segment = new Segment(); + + segment.Deleted = data.ReadUInt32(); + segment.FileLocation = data.ReadUInt32(); + segment.FileSize = data.ReadUInt32(); + segment.PackedDate = data.ReadUInt32(); + byte[] fileName = data.ReadBytes(0x10); + segment.FileName = Encoding.ASCII.GetString(fileName).TrimEnd('\0'); + if (segmentSize > Version2SegmentSize) + segment.ModifiedDate = data.ReadUInt32(); + if (segmentSize > Version3SegmentSize) + segment.CompressionLevel = data.ReadUInt32(); + + return segment; + } + } +} \ No newline at end of file diff --git a/Streams/PFF.Serializer.cs b/Streams/PFF.Serializer.cs new file mode 100644 index 00000000..ce481d42 --- /dev/null +++ b/Streams/PFF.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.PFF; + +namespace SabreTools.Serialization.Streams +{ + public partial class PFF : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Archive obj) => throw new NotImplementedException(); +#else + public Stream? Serialize(Archive? obj) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file