diff --git a/Bytes/Quantum.Deserializer.cs b/Bytes/Quantum.Deserializer.cs new file mode 100644 index 00000000..83334258 --- /dev/null +++ b/Bytes/Quantum.Deserializer.cs @@ -0,0 +1,28 @@ +using System.IO; +using SabreTools.Models.Quantum; + +namespace SabreTools.Serialization.Bytes +{ + public partial class Quantum : 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.Quantum().Deserialize(dataStream); + } + } +} \ No newline at end of file diff --git a/Streams/Quantum.Deserializer.cs b/Streams/Quantum.Deserializer.cs new file mode 100644 index 00000000..1e0f37e5 --- /dev/null +++ b/Streams/Quantum.Deserializer.cs @@ -0,0 +1,155 @@ +using System.IO; +using System.Text; +using SabreTools.IO; +using SabreTools.Models.Quantum; +using static SabreTools.Models.Quantum.Constants; + +namespace SabreTools.Serialization.Streams +{ + public partial class Quantum : 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 File List + + // If we have any files + if (header.FileCount > 0) + { + var fileDescriptors = new FileDescriptor[header.FileCount]; + + // Read all entries in turn + for (int i = 0; i < header.FileCount; i++) + { + var file = ParseFileDescriptor(data, header.MinorVersion); + if (file == null) + return null; + + fileDescriptors[i] = file; + } + + // Set the file list + archive.FileList = fileDescriptors; + } + + #endregion + + // Cache the compressed data offset + archive.CompressedDataOffset = data.Position; + + 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(); + + byte[] signature = data.ReadBytes(2); + header.Signature = Encoding.ASCII.GetString(signature); + if (header.Signature != SignatureString) + return null; + + header.MajorVersion = data.ReadByteValue(); + header.MinorVersion = data.ReadByteValue(); + header.FileCount = data.ReadUInt16(); + header.TableSize = data.ReadByteValue(); + header.CompressionFlags = data.ReadByteValue(); + + return header; + } + + /// + /// Parse a Stream into a file descriptor + /// + /// Stream to parse + /// Minor version of the archive + /// Filled file descriptor on success, null on error + private static FileDescriptor ParseFileDescriptor(Stream data, byte minorVersion) + { + // TODO: Use marshalling here instead of building + FileDescriptor fileDescriptor = new FileDescriptor(); + + fileDescriptor.FileNameSize = ReadVariableLength(data); + if (fileDescriptor.FileNameSize > 0) + { + byte[] fileName = data.ReadBytes(fileDescriptor.FileNameSize); + fileDescriptor.FileName = Encoding.ASCII.GetString(fileName); + } + + fileDescriptor.CommentFieldSize = ReadVariableLength(data); + if (fileDescriptor.CommentFieldSize > 0) + { + byte[] commentField = data.ReadBytes(fileDescriptor.CommentFieldSize); + fileDescriptor.CommentField = Encoding.ASCII.GetString(commentField); + } + + fileDescriptor.ExpandedFileSize = data.ReadUInt32(); + fileDescriptor.FileTime = data.ReadUInt16(); + fileDescriptor.FileDate = data.ReadUInt16(); + + // Hack for unknown format data + if (minorVersion == 22) + fileDescriptor.Unknown = data.ReadUInt16(); + + return fileDescriptor; + } + + /// + /// Parse a Stream into a variable-length size prefix + /// + /// Stream to parse + /// Variable-length size prefix + /// + /// Strings are prefixed with their length. If the length is less than 128 + /// then it is stored directly in one byte. If it is greater than 127 then + /// the high bit of the first byte is set to 1 and the remaining fifteen bits + /// contain the actual length in big-endian format. + /// + private static int ReadVariableLength(Stream data) + { + byte b0 = data.ReadByteValue(); + if (b0 < 0x7F) + return b0; + + b0 &= 0x7F; + byte b1 = data.ReadByteValue(); + return (b0 << 8) | b1; + } + } +} \ No newline at end of file diff --git a/Streams/Quantum.Serializer.cs b/Streams/Quantum.Serializer.cs new file mode 100644 index 00000000..bd3548ce --- /dev/null +++ b/Streams/Quantum.Serializer.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using SabreTools.Models.Quantum; + +namespace SabreTools.Serialization.Streams +{ + public partial class Quantum : 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